<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <script id="versionArea" type="text/javascript">
    //<![CDATA[
    var version = {title: "TiddlyWiki", major: 2, minor: 4, revision: 1, date: new Date("Aug 4, 2008"), extensions: {}};
    //]]>
  </script>
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <meta name="copyright" content="
TiddlyWiki created by Jeremy Ruston, (jeremy [at] osmosoft [dot] com)

Copyright (c) UnaMesa Association 2004-2008

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.

Neither the name of the UnaMesa Association nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
" />
  <!--PRE-HEAD-START-->
<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' />
<!--}}}-->
<!--PRE-HEAD-END-->
  <title> d3 - do it, delegate it, or defer it </title>
  <style id="styleArea" type="text/css">
    #saveTest {
      display: none;
    }

    #messageArea {
      display: none;
    }

    #copyright {
      display: none;
    }

    #storeArea {
      display: none;
    }

    #storeArea div {
      padding: 0.5em;
      margin: 1em 0em 0em 0em;
      border-color: #fff #666 #444 #ddd;
      border-style: solid;
      border-width: 2px;
      overflow: auto;
    }

    #shadowArea {
      display: none;
    }

    #javascriptWarning {
      width: 100%;
      text-align: center;
      font-weight: bold;
      background-color: #dd1100;
      color: #fff;
      padding: 1em 0em;
    }
  </style>
  <!--POST-HEAD-START-->

<!--POST-HEAD-END-->
</head>
<body onload="main();"
      onunload="if(window.checkUnsavedChanges) checkUnsavedChanges(); if(window.scrubNodes) scrubNodes(document.body);">
<!--PRE-BODY-START-->

<!--PRE-BODY-END-->
<div id="copyright">
  Welcome to TiddlyWiki created by Jeremy Ruston, Copyright &copy; 2007 UnaMesa Association
</div>
<noscript>
  <div id="javascriptWarning">This page requires JavaScript to function properly.<br /><br />If you are using Microsoft Internet Explorer you may need to click on the yellow bar above and select 'Allow Blocked Content'. You must then click 'Yes' on the following security warning.
  </div>
</noscript>
<div id="saveTest"></div>
<div id="backstageCloak"></div>
<div id="backstageButton"></div>
<div id="backstageArea">
  <div id="backstageToolbar"></div>
</div>
<div id="backstage">
  <div id="backstagePanel"></div>
</div>
<div id="contentWrapper"></div>
<div id="contentStash"></div>
<div id="shadowArea">
<div title="MarkupPreHead">
<pre>&lt;!--{{{--&gt;
&lt;link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' /&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="ColorPalette">
<pre>Background: #fff
Foreground: #000
PrimaryPale: #8cf
PrimaryLight: #18f
PrimaryMid: #04b
PrimaryDark: #014
SecondaryPale: #ffc
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88</pre>
</div>
<div title="StyleSheetColors">
<pre>/*{{{*/
body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}

a {color:[[ColorPalette::PrimaryMid]];}
a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
a img {border:0;}

h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}

.button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
.button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
.button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}

.header {background:[[ColorPalette::PrimaryMid]];}
.headerShadow {color:[[ColorPalette::Foreground]];}
.headerShadow a {font-weight:normal; color:[[ColorPalette::Foreground]];}
.headerForeground {color:[[ColorPalette::Background]];}
.headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];}

.tabSelected{color:[[ColorPalette::PrimaryDark]];
	background:[[ColorPalette::TertiaryPale]];
	border-left:1px solid [[ColorPalette::TertiaryLight]];
	border-top:1px solid [[ColorPalette::TertiaryLight]];
	border-right:1px solid [[ColorPalette::TertiaryLight]];
}
.tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
.tabContents {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];}
.tabContents .button {border:0;}

#sidebar {}
#sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
#sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}

.wizard {background:[[ColorPalette::PrimaryPale]]; border:1px solid [[ColorPalette::PrimaryMid]];}
.wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
.wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
.wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
	border:1px solid [[ColorPalette::PrimaryMid]];}
.wizardStep.wizardStepDone {background:[[ColorPalette::TertiaryLight]];}
.wizardFooter {background:[[ColorPalette::PrimaryPale]];}
.wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
.wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
	border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
.wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
.wizard .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
	border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];}
	
.wizard .notChanged {background:transparent;}
.wizard .changedLocally {background:#80ff80;}
.wizard .changedServer {background:#8080ff;}
.wizard .changedBoth {background:#ff8080;}
.wizard .notFound {background:#ffff80;}
.wizard .putToServer {background:#ff80ff;}
.wizard .gotFromServer {background:#80ffff;}

#messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
#messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}

.popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}

.popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
.popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
.popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
.popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
.popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
.listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}

.tiddler .defaultCommand {font-weight:bold;}

.shadow .title {color:[[ColorPalette::TertiaryDark]];}

.title {color:[[ColorPalette::SecondaryDark]];}
.subtitle {color:[[ColorPalette::TertiaryDark]];}

.toolbar {color:[[ColorPalette::PrimaryMid]];}
.toolbar a {color:[[ColorPalette::TertiaryLight]];}
.selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
.selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}

.tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
.selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
.tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
.tagging .button, .tagged .button {border:none;}

.footer {color:[[ColorPalette::TertiaryLight]];}
.selected .footer {color:[[ColorPalette::TertiaryMid]];}

.sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
.sparktick {background:[[ColorPalette::PrimaryDark]];}

.error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
.warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
.lowlight {background:[[ColorPalette::TertiaryLight]];}

.zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}

.imageLink, #displayArea .imageLink {background:transparent;}

.annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}

.viewer .listTitle {list-style-type:none; margin-left:-2em;}
.viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
.viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}

.viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
.viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
.viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}

.viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
.viewer code {color:[[ColorPalette::SecondaryDark]];}
.viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}

.highlight, .marked {background:[[ColorPalette::SecondaryLight]];}

.editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
.editorFooter {color:[[ColorPalette::TertiaryMid]];}

#backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
#backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
#backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
#backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
#backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
.backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
.backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
#backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:'alpha(opacity:60)';}
/*}}}*/</pre>
</div>
<div title="StyleSheetLayout">
<pre>/*{{{*/
* html .tiddler {height:1%;}

body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}

h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
h4,h5,h6 {margin-top:1em;}
h1 {font-size:1.35em;}
h2 {font-size:1.25em;}
h3 {font-size:1.1em;}
h4 {font-size:1em;}
h5 {font-size:.9em;}

hr {height:1px;}

a {text-decoration:none;}

dt {font-weight:bold;}

ol {list-style-type:decimal;}
ol ol {list-style-type:lower-alpha;}
ol ol ol {list-style-type:lower-roman;}
ol ol ol ol {list-style-type:decimal;}
ol ol ol ol ol {list-style-type:lower-alpha;}
ol ol ol ol ol ol {list-style-type:lower-roman;}
ol ol ol ol ol ol ol {list-style-type:decimal;}

.txtOptionInput {width:11em;}

#contentWrapper .chkOptionInput {border:0;}

.externalLink {text-decoration:underline;}

.indent {margin-left:3em;}
.outdent {margin-left:3em; text-indent:-3em;}
code.escaped {white-space:nowrap;}

.tiddlyLinkExisting {font-weight:bold;}
.tiddlyLinkNonExisting {font-style:italic;}

/* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
a.tiddlyLinkNonExisting.shadow {font-weight:bold;}

#mainMenu .tiddlyLinkExisting,
	#mainMenu .tiddlyLinkNonExisting,
	#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
#sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}

.header {position:relative;}
.header a:hover {background:transparent;}
.headerShadow {position:relative; padding:4.5em 0em 1em 1em; left:-1px; top:-1px;}
.headerForeground {position:absolute; padding:4.5em 0em 1em 1em; left:0px; top:0px;}

.siteTitle {font-size:3em;}
.siteSubtitle {font-size:1.2em;}

#mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}

#sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
#sidebarOptions {padding-top:0.3em;}
#sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
#sidebarOptions input {margin:0.4em 0.5em;}
#sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
#sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
#sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
#sidebarTabs .tabContents {width:15em; overflow:hidden;}

.wizard {padding:0.1em 1em 0em 2em;}
.wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizardStep {padding:1em 1em 1em 1em;}
.wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
.wizardFooter {padding:0.8em 0.4em 0.8em 0em;}
.wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
.wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}

#messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
.messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
#messageArea a {text-decoration:underline;}

.tiddlerPopupButton {padding:0.2em 0.2em 0.2em 0.2em;}
.popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}

.popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
.popup .popupMessage {padding:0.4em;}
.popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
.popup li.disabled {padding:0.4em;}
.popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
.listBreak {font-size:1px; line-height:1px;}
.listBreak div {margin:2px 0;}

.tabset {padding:1em 0em 0em 0.5em;}
.tab {margin:0em 0em 0em 0.25em; padding:2px;}
.tabContents {padding:0.5em;}
.tabContents ul, .tabContents ol {margin:0; padding:0;}
.txtMainTab .tabContents li {list-style:none;}
.tabContents li.listLink { margin-left:.75em;}

#contentWrapper {display:block;}
#splashScreen {display:none;}

#displayArea {margin:1em 17em 0em 14em;}

.toolbar {text-align:right; font-size:.9em;}

.tiddler {padding:1em 1em 0em 1em;}

.missing .viewer,.missing .title {font-style:italic;}

.title {font-size:1.6em; font-weight:bold;}

.missing .subtitle {display:none;}
.subtitle {font-size:1.1em;}

.tiddler .button {padding:0.2em 0.4em;}

.tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
.isTag .tagging {display:block;}
.tagged {margin:0.5em; float:right;}
.tagging, .tagged {font-size:0.9em; padding:0.25em;}
.tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
.tagClear {clear:both;}

.footer {font-size:.9em;}
.footer li {display:inline;}

.annotation {padding:0.5em; margin:0.5em;}

* html .viewer pre {width:99%; padding:0 0 1em 0;}
.viewer {line-height:1.4em; padding-top:0.5em;}
.viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
.viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
.viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}

.viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
.viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
table.listView {font-size:0.85em; margin:0.8em 1.0em;}
table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}

.viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
.viewer code {font-size:1.2em; line-height:1.4em;}

.editor {font-size:1.1em;}
.editor input, .editor textarea {display:block; width:100%; font:inherit;}
.editorFooter {padding:0.25em 0em; font-size:.9em;}
.editorFooter .button {padding-top:0px; padding-bottom:0px;}

.fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}

.sparkline {line-height:1em;}
.sparktick {outline:0;}

.zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
.zoomer div {padding:1em;}

* html #backstage {width:99%;}
* html #backstageArea {width:99%;}
#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageToolbar {position:relative;}
#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
#backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
#backstage {position:relative; width:100%; z-index:50;}
#backstagePanel {display:none; z-index:100; position:absolute; width:90%; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
.backstagePanelFooter {padding-top:0.2em; float:right;}
.backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}

.whenBackstage {display:none;}
.backstageVisible .whenBackstage {display:block;}
/*}}}*/</pre>
</div>
<div title="StyleSheetLocale">
<pre>/***
StyleSheet for use when a translation requires any css style changes.
This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
***/
/*{{{*/
body {font-size:0.8em;}
#sidebarOptions {font-size:1.05em;}
#sidebarOptions a {font-style:normal;}
#sidebarOptions .sliderPanel {font-size:0.95em;}
.subtitle {font-size:0.8em;}
.viewer table.listView {font-size:0.95em;}
/*}}}*/</pre>
</div>
<div title="StyleSheetPrint">
<pre>/*{{{*/
@media print {
#mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none ! important;}
#displayArea {margin: 1em 1em 0em 1em;}
/* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
noscript {display:none;}
}
/*}}}*/</pre>
</div>
<div title="PageTemplate">
<pre>&lt;!--{{{--&gt;
&lt;div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'&gt;
&lt;div class='headerShadow'&gt;
&lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
&lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class='headerForeground'&gt;
&lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
&lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id='mainMenu' refresh='content' tiddler='MainMenu'&gt;&lt;/div&gt;
&lt;div id='sidebar'&gt;
&lt;div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'&gt;&lt;/div&gt;
&lt;div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id='displayArea'&gt;
&lt;div id='messageArea'&gt;&lt;/div&gt;
&lt;div id='tiddlerDisplay'&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="ViewTemplate">
<pre>&lt;!--{{{--&gt;
&lt;div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='subtitle'&gt;&lt;span macro='view modifier link'&gt;&lt;/span&gt;, &lt;span macro='view modified date'&gt;&lt;/span&gt; (&lt;span macro='message views.wikified.createdPrompt'&gt;&lt;/span&gt; &lt;span macro='view created date'&gt;&lt;/span&gt;)&lt;/div&gt;
&lt;div class='tagging' macro='tagging'&gt;&lt;/div&gt;
&lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
&lt;div class='viewer' macro='view text wikified'&gt;&lt;/div&gt;
&lt;div class='tagClear'&gt;&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="EditTemplate">
<pre>&lt;!--{{{--&gt;
&lt;div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;
&lt;div macro='annotations'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit tags'&gt;&lt;/div&gt;&lt;div class='editorFooter'&gt;&lt;span macro='message views.editor.tagPrompt'&gt;&lt;/span&gt;&lt;span macro='tagChooser'&gt;&lt;/span&gt;&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="GettingStarted">
<pre>To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
* SiteTitle &amp; SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
* MainMenu: The menu (usually on the left)
* DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
You'll also need to enter your username for signing your edits: &lt;&lt;option txtUserName&gt;&gt;</pre>
</div>
<div title="OptionsPanel">
<pre>These InterfaceOptions for customising TiddlyWiki are saved in your browser

Your username for signing your edits. Write it as a WikiWord (eg JoeBloggs)

&lt;&lt;option txtUserName&gt;&gt;
&lt;&lt;option chkSaveBackups&gt;&gt; SaveBackups
&lt;&lt;option chkAutoSave&gt;&gt; AutoSave
&lt;&lt;option chkRegExpSearch&gt;&gt; RegExpSearch
&lt;&lt;option chkCaseSensitiveSearch&gt;&gt; CaseSensitiveSearch
&lt;&lt;option chkAnimate&gt;&gt; EnableAnimations

----
Also see AdvancedOptions</pre>
</div>
<div title="ImportTiddlers">
  <pre>&lt;&lt;importTiddlers&gt;&gt;</pre>
</div>
</div>
<!--POST-SHADOWAREA-->
<div id="storeArea">
<div title="@IT1" modifier="ImaiMasanobu" created="200905190207" modified="200905260222" tags="context" changecount="8">
<pre>!基本機能の実装

!!画面遷移図
[img[it1-flow.jpg]]

!!画面レイアウト
[img[it1-layout.jpg]]

!!やること
&lt;&lt;gtdActionList&gt;&gt;</pre>
</div>
<div title="@IT2" modifier="ImaiMasanobu" created="200905190210" modified="200906110714" tags="context" changecount="6">
<pre>!基本機能への装飾

!Continuos Paginationのイメージ
[img[it2-ajax-paging.png]]

!この時点でのドメインクラスのリレーション図
[img[domain-it01-02.png]]

----
&lt;&lt;gtdActionList&gt;&gt;</pre>
</div>
<div title="@IT3" modifier="ImaiMasanobu" created="200905190213" modified="200906040604" tags="context" changecount="7">
<pre>!お気に入りの設定

!!画面遷移図
[img[it3-flow.png]]

!!概念モデル
[img[it3-model.png]]

!!やること
&lt;&lt;gtdActionList&gt;&gt;

!!実装モデル
[img[domain-it03.png]]

!!非同期の仕組み
[img[it3-architecture.jpg]]</pre>
</div>
<div title="@IT4" modifier="ImaiMasanobu" created="200905190219" modified="200906110719" tags="context" changecount="3">
<pre>!メッセージの送受信
!!概念モデル
[img[it4-model.png]]

タイムライン同様，メッセージ受信用の箱（Reply)を持つ。

!!画面遷移
[img[it4-flow.png]]

!!画面レイアウト
[img[it4-layout.png]]

!!出来上がったドメイン
[img[it4-domain.png]]

&lt;&lt;gtdActionList&gt;&gt;</pre>
</div>
<div title="@IT5" modifier="ImaiMasanobu" created="200905190220" modified="200906110728" tags="context" changecount="3">
<pre>!画面装飾

!!出来上がったドメイン
[img[it5-domain.png]]
イテレーション４から増えたドメインは，矢印が付いているAvatorIconだけ。
AccountとのEager Fetch対策のため，あえてAccountとは関連づけしていない（ロジックで関連づけている）。

&lt;&lt;gtdActionList&gt;&gt;</pre>
</div>
<div title="@unfiled" modifier="ImaiMasanobu" created="200905150654" modified="200906040543" tags="context" changecount="8" creator="ImaiMasanobu" gtd.context="@unfiled">
<pre>余力があればこれも。
# ユーザ一覧画面でユーザを検索できる。
# ユーザ一覧画面で登録済みのユーザがわかる。
# ユーザ一覧画面でユーザをお気に入り登録できる。


&lt;&lt;gtdActionList&gt;&gt;</pre>
</div>
<div title="@必ずやる" modifier="ImaiMasanobu" created="200905150623" modified="200905150628" tags="context" changecount="5" creator="ImaiMasanobu">
<pre>&lt;&lt;gtdActionList&gt;&gt;</pre>
</div>
<div title="About" modifier="TomO" created="200603030000" modified="200603301851">
<pre>This notebook attempts to capture the essence of a [[&quot;kinkless&quot; GTD system|http://www.kinkless.com]] using TiddlyWiki. It is using the &lt;&lt;gtdVersion&gt;&gt; version of the GTD plugins, by Tom Otvos, and is based on version &lt;&lt;version&gt;&gt; of the [[TiddlyWiki|http://www.tiddlywiki.com]] stand-alone wiki project, by Jeremy Ruston. For customization info, see [[GTD TiddlyWiki|http://groups.google.com/group/GTD-TiddlyWiki]] and [[TiddlyWikiDev|http://www.tiddlywiki.com/dev/]].</pre>
</div>
<div title="About version 1.3.0" modifier="ImaiMasanobu" created="200809301615" modified="200906240540" tags="gtd" changecount="63">
<pre>クリックするとスライド開始 → [[アーキテクチャ解説|./twitterclone.html]]
</pre>
</div>
<div title="Action Review" modifier="TomO" created="200603020500" modified="200610231248" tags="gtd review">
<pre>! Next actions for active contexts
&lt;&lt;gtdActionList @&gt;&gt;</pre>
</div>
<div title="Archives" modifier="TomO" created="200603291846" modified="200603291846" tags="gtd">
<pre>To make this system operate more efficiently, you should periodically archive completed projects and actions. When a project or action is archived, it is merely tagged in a special way to get it &quot;out of sight&quot;, but all the information in the project and action tiddlers is preserved. This is important if you need to go back and find something. Click one of these buttons to view the current &lt;&lt;tag project-archive&gt;&gt; or &lt;&lt;tag action-archive&gt;&gt;.

** Click &lt;&lt;gtdArchive archive&gt;&gt; if you would like to archive all completed projects and actions now.
** Click &lt;&lt;gtdArchive unarchive&gt;&gt; if you would like to unarchive all previously archived projects and actions now.

If you are sure that you do not want to retain archived projects and actions, you can purge them completely from the system. //Once these archived items are removed, the only way they can be put back in is through manual importing or copy/paste.// ''For your safety, your file will be saved and a backup file will be automatically generated before an archive purge is performed.''

** Click &lt;&lt;gtdArchive purge&gt;&gt; if you would like to purge your archive now.</pre>
</div>
<div title="CalendarPlugin" modifier="ELSDesignStudios" created="200602212149" modified="200806170935" tags="systemConfig">
<pre>/***
|Name|CalendarPlugin|
|Source|http://www.TiddlyTools.com/#CalendarPlugin|
|Version|2008.06.17|
|Author|Eric Shulman|
|Original Author|SteveRumsby|
|License|unknown|
|~CoreVersion|2.1|
|Type|plugin|
|Requires||
|Overrides||
|Options|##Configuration|
|Description|display monthly and yearly calendars|

NOTE: For enhanced date display (including popups), you must also install [[DatePlugin]]
!!!!!Usage:
&lt;&lt;&lt;
|{{{&lt;&lt;calendar&gt;&gt;}}}|Produce a full-year calendar for the current year|
|{{{&lt;&lt;calendar year&gt;&gt;}}}|Produce a full-year calendar for the given year|
|{{{&lt;&lt;calendar year month&gt;&gt;}}}|Produce a one-month calendar for the given month and year|
|{{{&lt;&lt;calendar thismonth&gt;&gt;}}}|Produce a one-month calendar for the current month|
|{{{&lt;&lt;calendar lastmonth&gt;&gt;}}}|Produce a one-month calendar for last month|
|{{{&lt;&lt;calendar nextmonth&gt;&gt;}}}|Produce a one-month calendar for next month|
&lt;&lt;&lt;
!!!!!Configuration:
&lt;&lt;&lt;
|''First day of week:''&lt;br&gt;{{{config.options.txtCalFirstDay}}}|&lt;&lt;option txtCalFirstDay&gt;&gt;|(Monday = 0, Sunday = 6)|
|''First day of weekend:''&lt;br&gt;{{{config.options.txtCalStartOfWeekend}}}|&lt;&lt;option txtCalStartOfWeekend&gt;&gt;|(Monday = 0, Sunday = 6)|

&lt;&lt;option chkDisplayWeekNumbers&gt;&gt; Display week numbers //(note: Monday will be used as the start of the week)//
|''Week number display format:''&lt;br&gt;{{{config.options.txtWeekNumberDisplayFormat }}}|&lt;&lt;option txtWeekNumberDisplayFormat &gt;&gt;|
|''Week number link format:''&lt;br&gt;{{{config.options.txtWeekNumberLinkFormat }}}|&lt;&lt;option txtWeekNumberLinkFormat &gt;&gt;|
&lt;&lt;&lt;
!!!!!Revisions
&lt;&lt;&lt;
2008.06.17: added support for config.macros.calendar.todaybg
2008.02.27: in handler(), DON'T set hard-coded default date format, so that *customized* value (pre-defined in config.macros.calendar.journalDateFmt is used.
2008.02.17: in createCalendarYear(), fix next/previous year calculation (use parseInt() to convert to numeric value).  Also, use journalDateFmt for date linking when NOT using [[DatePlugin]].
2008.02.16: in createCalendarDay(), week numbers now created as TiddlyLinks, allowing quick creation/navigation to 'weekly' journals (based on request from Kashgarinn)
2008.01.08: in createCalendarMonthHeader(), &quot;month year&quot; heading is now created as TiddlyLink, allowing quick creation/navigation to 'month-at-a-time' journals
2007.11.30: added &quot;return false&quot; to onclick handlers (prevent IE from opening blank pages)
2006.08.23: added handling for weeknumbers (code supplied by Martin Budden (see &quot;wn**&quot; comment marks).  Also, incorporated updated by Jeremy Sheeley to add caching for reminders (see [[ReminderMacros]], if installed)
2005.10.30: in config.macros.calendar.handler(), use &quot;tbody&quot; element for IE compatibility.  Also, fix year calculation for IE's getYear() function (which returns '2005' instead of '105'). Also, in createCalendarDays(), use showDate() function (see [[DatePlugin]], if installed) to render autostyled date with linked popup.  Updated calendar stylesheet definition: use .calendar class-specific selectors, add text centering and margin settings
2006.05.29: added journalDateFmt handling
&lt;&lt;&lt;
***/
/***
!!!!!Code section:
***/
//{{{
version.extensions.calendar = { major: 0, minor: 7, revision: 0, date: new Date(2008, 6, 17)};

if(config.options.txtCalFirstDay == undefined)
  config.options.txtCalFirstDay = 0;
if(config.options.txtCalStartOfWeekend == undefined)
  config.options.txtCalStartOfWeekend = 5;
if(config.options.chkDisplayWeekNumbers == undefined)//wn**
  config.options.chkDisplayWeekNumbers = false;
if(config.options.chkDisplayWeekNumbers)
  config.options.txtCalFirstDay = 0;
if(config.options.txtWeekNumberDisplayFormat == undefined)//wn**
  config.options.txtWeekNumberDisplayFormat = &quot;w0WW&quot;;
if(config.options.txtWeekNumberLinkFormat == undefined)//wn**
  config.options.txtWeekNumberLinkFormat = &quot;YYYY-w0WW&quot;;

config.macros.calendar = {};
config.macros.calendar.monthnames = [&quot;Jan&quot;, &quot;Feb&quot;, &quot;Mar&quot;, &quot;Apr&quot;, &quot;May&quot;, &quot;Jun&quot;, &quot;Jul&quot;, &quot;Aug&quot;, &quot;Sep&quot;, &quot;Oct&quot;, &quot;Nov&quot;, &quot;Dec&quot;];
config.macros.calendar.daynames = [&quot;M&quot;, &quot;T&quot;, &quot;W&quot;, &quot;T&quot;, &quot;F&quot;, &quot;S&quot;, &quot;S&quot;];
config.macros.calendar.todaybg = &quot;#ccccff&quot;;
config.macros.calendar.weekendbg = &quot;#c0c0c0&quot;;
config.macros.calendar.monthbg = &quot;#e0e0e0&quot;;
config.macros.calendar.holidaybg = &quot;#ffc0c0&quot;;
config.macros.calendar.journalDateFmt = &quot;DD MMM YYYY&quot;;
config.macros.calendar.monthdays = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
config.macros.calendar.holidays = [ ]; // Not sure this is required anymore - use reminders instead
//}}}
//{{{
function calendarIsHoliday(date) // Is the given date a holiday?
{
	var longHoliday = date.formatString(&quot;0DD/0MM/YYYY&quot;);
	var shortHoliday = date.formatString(&quot;0DD/0MM&quot;);
	for(var i = 0; i &lt; config.macros.calendar.holidays.length; i++) {
		if(config.macros.calendar.holidays[i] == longHoliday || config.macros.calendar.holidays[i] == shortHoliday)
			return true;
	}
	return false;
}
//}}}
//{{{
config.macros.calendar.handler = function(place,macroName,params) {
	var calendar = createTiddlyElement(place, &quot;table&quot;, null, &quot;calendar&quot;, null);
	var tbody = createTiddlyElement(calendar, &quot;tbody&quot;, null, null, null);
	var today = new Date();
	var year = today.getYear();
	if (year&lt;1900) year+=1900;

 	// get format for journal link by reading from SideBarOptions (ELS 5/29/06 - based on suggestion by Martin Budden)
	var text = store.getTiddlerText(&quot;SideBarOptions&quot;);
	var re = new RegExp(&quot;&lt;&lt;(?:newJournal)([^&gt;]*)&gt;&gt;&quot;,&quot;mg&quot;); var fm = re.exec(text);
	if (fm &amp;&amp; fm[1]!=null) { var pa=fm[1].readMacroParams(); if (pa[0]) this.journalDateFmt = pa[0]; }

	if (params[0] == &quot;thismonth&quot;) {
		cacheReminders(new Date(year, today.getMonth(), 1, 0, 0), 31);
		createCalendarOneMonth(tbody, year, today.getMonth());
	} 
	else if (params[0] == &quot;lastmonth&quot;) {
		var month = today.getMonth()-1; if (month==-1) { month=11; year--; }
		cacheReminders(new Date(year, month, 1, 0, 0), 31);
		createCalendarOneMonth(tbody, year, month);
	}
	else if (params[0] == &quot;nextmonth&quot;) {
		var month = today.getMonth()+1; if (month&gt;11) { month=0; year++; }
		cacheReminders(new Date(year, month, 1, 0, 0), 31);
		createCalendarOneMonth(tbody, year, month);
	} else {
		if (params[0]) year = params[0];
		if(params[1]) {
			cacheReminders(new Date(year, params[1]-1, 1, 0, 0), 31);
			createCalendarOneMonth(tbody, year, params[1]-1);
		} else {
			cacheReminders(new Date(year, 0, 1, 0, 0), 366);
			createCalendarYear(tbody, year);
		}
	}
	window.reminderCacheForCalendar = null;
}
//}}}
//{{{
//This global variable is used to store reminders that have been cached
//while the calendar is being rendered.  It will be renulled after the calendar is fully rendered.
window.reminderCacheForCalendar = null;
//}}}
//{{{
function cacheReminders(date, leadtime)
{
	if (window.findTiddlersWithReminders == null) return;
	window.reminderCacheForCalendar = {};
	var leadtimeHash = [];
	leadtimeHash [0] = 0;
	leadtimeHash [1] = leadtime;
	var t = findTiddlersWithReminders(date, leadtimeHash, null, 1);
	for(var i = 0; i &lt; t.length; i++) {
		//just tag it in the cache, so that when we're drawing days, we can bold this one.
		window.reminderCacheForCalendar[t[i][&quot;matchedDate&quot;]] = &quot;reminder:&quot; + t[i][&quot;params&quot;][&quot;title&quot;]; 
	}
}
//}}}
//{{{
function createCalendarOneMonth(calendar, year, mon)
{
	var row = createTiddlyElement(calendar, &quot;tr&quot;, null, null, null);
	createCalendarMonthHeader(calendar, row, config.macros.calendar.monthnames[mon] + &quot; &quot; + year, true, year, mon);
	row = createTiddlyElement(calendar, &quot;tr&quot;, null, null, null);
	createCalendarDayHeader(row, 1);
	createCalendarDayRowsSingle(calendar, year, mon);
}
//}}}
//{{{
function createCalendarMonth(calendar, year, mon)
{
	var row = createTiddlyElement(calendar, &quot;tr&quot;, null, null, null);
	createCalendarMonthHeader(calendar, row, config.macros.calendar.monthnames[mon] + &quot; &quot; + year, false, year, mon);
	row = createTiddlyElement(calendar, &quot;tr&quot;, null, null, null);
	createCalendarDayHeader(row, 1);
	createCalendarDayRowsSingle(calendar, year, mon);
}
//}}}
//{{{
function createCalendarYear(calendar, year)
{
	var row;
	row = createTiddlyElement(calendar, &quot;tr&quot;, null, null, null);
	var back = createTiddlyElement(row, &quot;td&quot;, null, null, null);
	var backHandler = function() {
		removeChildren(calendar);
		createCalendarYear(calendar, parseInt(year)-1);
		return false; // consume click
	};
	createTiddlyButton(back, &quot;&lt;&quot;, &quot;Previous year&quot;, backHandler);
	back.align = &quot;center&quot;;
	var yearHeader = createTiddlyElement(row, &quot;td&quot;, null, &quot;calendarYear&quot;, year);
	yearHeader.align = &quot;center&quot;;
	yearHeader.setAttribute(&quot;colSpan&quot;,config.options.chkDisplayWeekNumbers?22:19);//wn**
	var fwd = createTiddlyElement(row, &quot;td&quot;, null, null, null);
	var fwdHandler = function() {
		removeChildren(calendar);
		createCalendarYear(calendar, parseInt(year)+1);
		return false; // consume click
	};
	createTiddlyButton(fwd, &quot;&gt;&quot;, &quot;Next year&quot;, fwdHandler);
	fwd.align = &quot;center&quot;;
	createCalendarMonthRow(calendar, year, 0);
	createCalendarMonthRow(calendar, year, 3);
	createCalendarMonthRow(calendar, year, 6);
	createCalendarMonthRow(calendar, year, 9);
}
//}}}
//{{{
function createCalendarMonthRow(cal, year, mon)
{
	var row = createTiddlyElement(cal, &quot;tr&quot;, null, null, null);
	createCalendarMonthHeader(cal, row, config.macros.calendar.monthnames[mon], false, year, mon);
	createCalendarMonthHeader(cal, row, config.macros.calendar.monthnames[mon+1], false, year, mon);
	createCalendarMonthHeader(cal, row, config.macros.calendar.monthnames[mon+2], false, year, mon);
	row = createTiddlyElement(cal, &quot;tr&quot;, null, null, null);
	createCalendarDayHeader(row, 3);
	createCalendarDayRows(cal, year, mon);
}
//}}}
//{{{
function createCalendarMonthHeader(cal, row, name, nav, year, mon)
{
	var month;
	if (nav) {
		var back = createTiddlyElement(row, &quot;td&quot;, null, null, null);
		back.align = &quot;center&quot;;
		back.style.background = config.macros.calendar.monthbg;

		var backMonHandler = function() {
			var newyear = year;
			var newmon = mon-1;
			if(newmon == -1) { newmon = 11; newyear = newyear-1;}
			removeChildren(cal);
			cacheReminders(new Date(newyear, newmon , 1, 0, 0), 31);
			createCalendarOneMonth(cal, newyear, newmon);
			return false; // consume click
		};
		createTiddlyButton(back, &quot;&lt;&quot;, &quot;Previous month&quot;, backMonHandler);
		month = createTiddlyElement(row, &quot;td&quot;, null, &quot;calendarMonthname&quot;)
		createTiddlyLink(month,name,true);
		month.setAttribute(&quot;colSpan&quot;, config.options.chkDisplayWeekNumbers?6:5);//wn**
		var fwd = createTiddlyElement(row, &quot;td&quot;, null, null, null);
		fwd.align = &quot;center&quot;;
		fwd.style.background = config.macros.calendar.monthbg; 

		var fwdMonHandler = function() {
			var newyear = year;
			var newmon = mon+1;
			if(newmon == 12) { newmon = 0; newyear = newyear+1;}
			removeChildren(cal);
			cacheReminders(new Date(newyear, newmon , 1, 0, 0), 31);
			createCalendarOneMonth(cal, newyear, newmon);
			return false; // consume click
		};
		createTiddlyButton(fwd, &quot;&gt;&quot;, &quot;Next month&quot;, fwdMonHandler);
	} else {
		month = createTiddlyElement(row, &quot;td&quot;, null, &quot;calendarMonthname&quot;, name)
		month.setAttribute(&quot;colSpan&quot;,config.options.chkDisplayWeekNumbers?8:7);//wn**
	}
	month.align = &quot;center&quot;;
	month.style.background = config.macros.calendar.monthbg;
}
//}}}
//{{{
function createCalendarDayHeader(row, num)
{
	var cell;
	for(var i = 0; i &lt; num; i++) {
		if (config.options.chkDisplayWeekNumbers) createTiddlyElement(row, &quot;td&quot;);//wn**
		for(var j = 0; j &lt; 7; j++) {
			var d = j + (config.options.txtCalFirstDay - 0);
			if(d &gt; 6) d = d - 7;
			cell = createTiddlyElement(row, &quot;td&quot;, null, null, config.macros.calendar.daynames[d]);
			if(d == (config.options.txtCalStartOfWeekend-0) || d == (config.options.txtCalStartOfWeekend-0+1))
				cell.style.background = config.macros.calendar.weekendbg;
		}
	}
}
//}}}
//{{{
function createCalendarDays(row, col, first, max, year, mon) {
	var i;
	if (config.options.chkDisplayWeekNumbers){
		if (first&lt;=max) {
			var ww = new Date(year,mon,first);
			var td=createTiddlyElement(row, &quot;td&quot;);//wn**
			var link=createTiddlyLink(td,ww.formatString(config.options.txtWeekNumberLinkFormat),false);
			link.appendChild(document.createTextNode(ww.formatString(config.options.txtWeekNumberDisplayFormat)));
		}
		else createTiddlyElement(row, &quot;td&quot;, null, null, null);//wn**
	}
	for(i = 0; i &lt; col; i++)
		createTiddlyElement(row, &quot;td&quot;, null, null, null);
	var day = first;
	for(i = col; i &lt; 7; i++) {
		var d = i + (config.options.txtCalFirstDay - 0);
		if(d &gt; 6) d = d - 7;
		var daycell = createTiddlyElement(row, &quot;td&quot;, null, null, null);
		var isaWeekend = ((d == (config.options.txtCalStartOfWeekend-0) || d == (config.options.txtCalStartOfWeekend-0+1))? true:false);
		if(day &gt; 0 &amp;&amp; day &lt;= max) {
			var celldate = new Date(year, mon, day);
			// ELS 2005.10.30: use &lt;&lt;date&gt;&gt; macro's showDate() function to create popup
			// ELS 5/29/06 - use journalDateFmt 
			if (window.showDate)
				showDate(daycell,celldate,&quot;popup&quot;,&quot;DD&quot;,config.macros.calendar.journalDateFmt,true, isaWeekend);
			else {
				if(isaWeekend) daycell.style.background = config.macros.calendar.weekendbg;
				var title = celldate.formatString(config.macros.calendar.journalDateFmt);
				if(calendarIsHoliday(celldate))
					daycell.style.background = config.macros.calendar.holidaybg;
				var now=new Date();
				if ((now-celldate&gt;=0) &amp;&amp; (now-celldate&lt;86400000)) // is today?
					daycell.style.background = config.macros.calendar.todaybg;
				if(window.findTiddlersWithReminders == null) {
					var link = createTiddlyLink(daycell, title, false);
					link.appendChild(document.createTextNode(day));
				} else
					var button = createTiddlyButton(daycell, day, title, onClickCalendarDate);
			}
		}
		day++;
	}
}
//}}}
//{{{
// We've clicked on a day in a calendar - create a suitable pop-up of options.
// The pop-up should contain:
//  * a link to create a new entry for that date
//  * a link to create a new reminder for that date
//  * an &lt;hr&gt;
//  * the list of reminders for that date
// NOTE: The following code is only used when [[DatePlugin]] is not present
function onClickCalendarDate(e)
{
	var button = this;
	var date = button.getAttribute(&quot;title&quot;);
	var dat = new Date(date.substr(6,4), date.substr(3,2)-1, date.substr(0, 2));

	date = dat.formatString(config.macros.calendar.journalDateFmt);
	var popup = createTiddlerPopup(this);
	popup.appendChild(document.createTextNode(date));
	var newReminder = function() {
		var t = store.getTiddlers(date);
		displayTiddler(null, date, 2, null, null, false, false);
		if(t) {
			document.getElementById(&quot;editorBody&quot; + date).value += &quot;\n&lt;&lt;reminder day:&quot; + dat.getDate() +
				&quot; month:&quot; + (dat.getMonth()+1) + &quot; year:&quot; + (dat.getYear()+1900) + &quot; title: &gt;&gt;&quot;;
		} else {
			document.getElementById(&quot;editorBody&quot; + date).value = &quot;&lt;&lt;reminder day:&quot; + dat.getDate() +
				&quot; month:&quot; + (dat.getMonth()+1) +&quot; year:&quot; + (dat.getYear()+1900) + &quot; title: &gt;&gt;&quot;;
		}
		return false; // consume click
	};
	var link = createTiddlyButton(popup, &quot;New reminder&quot;, null, newReminder); 
	popup.appendChild(document.createElement(&quot;hr&quot;));
	var t = findTiddlersWithReminders(dat, [0,14], null, 1);
	for(var i = 0; i &lt; t.length; i++) {
		link = createTiddlyLink(popup, t[i].tiddler, false);
		link.appendChild(document.createTextNode(t[i].tiddler));
	}
	return false; // consume click
}
//}}}
//{{{
function calendarMaxDays(year, mon)
{
	var max = config.macros.calendar.monthdays[mon];
	if(mon == 1 &amp;&amp; (year % 4) == 0 &amp;&amp; ((year % 100) != 0 || (year % 400) == 0)) max++;
	return max;
}
//}}}
//{{{
function createCalendarDayRows(cal, year, mon)
{
	var row = createTiddlyElement(cal, &quot;tr&quot;, null, null, null);
	var first1 = (new Date(year, mon, 1)).getDay() -1 - (config.options.txtCalFirstDay-0);
	if(first1 &lt; 0) first1 = first1 + 7;
	var day1 = -first1 + 1;
	var first2 = (new Date(year, mon+1, 1)).getDay() -1 - (config.options.txtCalFirstDay-0);
	if(first2 &lt; 0) first2 = first2 + 7;
	var day2 = -first2 + 1;
	var first3 = (new Date(year, mon+2, 1)).getDay() -1 - (config.options.txtCalFirstDay-0);
	if(first3 &lt; 0) first3 = first3 + 7;
	var day3 = -first3 + 1;

	var max1 = calendarMaxDays(year, mon);
	var max2 = calendarMaxDays(year, mon+1);
	var max3 = calendarMaxDays(year, mon+2);

	while(day1 &lt;= max1 || day2 &lt;= max2 || day3 &lt;= max3) {
		row = createTiddlyElement(cal, &quot;tr&quot;, null, null, null);
		createCalendarDays(row, 0, day1, max1, year, mon); day1 += 7;
		createCalendarDays(row, 0, day2, max2, year, mon+1); day2 += 7;
		createCalendarDays(row, 0, day3, max3, year, mon+2); day3 += 7;
	}
}
//}}}
//{{{
function createCalendarDayRowsSingle(cal, year, mon)
{
	var row = createTiddlyElement(cal, &quot;tr&quot;, null, null, null);
	var first1 = (new Date(year, mon, 1)).getDay() -1 - (config.options.txtCalFirstDay-0);
	if(first1 &lt; 0) first1 = first1+ 7;
	var day1 = -first1 + 1;
	var max1 = calendarMaxDays(year, mon);
	while(day1 &lt;= max1) {
		row = createTiddlyElement(cal, &quot;tr&quot;, null, null, null);
		createCalendarDays(row, 0, day1, max1, year, mon); day1 += 7;
	}
}
//}}}
//{{{
setStylesheet(&quot;.calendar, .calendar table, .calendar th, .calendar tr, .calendar td { text-align:center; } .calendar, .calendar a { margin:0px !important; padding:0px !important; }&quot;, &quot;calendarStyles&quot;);
//}}}</pre>
</div>
<div title="Configuration Options" modifier="TomO" created="200603020500" modified="200807101845" tags="gtd">
<pre>These configuration options enable you to customize the default behaviour of this wiki. They are saved locally as cookies, just like other TiddlyWiki configuration options. Note that in some cases, you will need to reload the document for the changes to take effect.

!!Action display settings
This value, if specified, is a semi-colon-delimited list of tags that are used to prioritize projects and actions for those projects. If left blank, the single priority tag &quot;important&quot; is used to sort projects. Otherwise, the specified tags are used in the listed order of importance, the first tag being most important (you will need to reload the document to see your change):
&lt;&lt;option txtGTDProjectPriorities&gt;&gt;

This value, if specified, is the number of days to keep completed actions in context and review action lists (leave blank to show all unarchived, completed actions):
&lt;&lt;option txtGTDActionAging&gt;&gt;

!!Magic tags
The following tag is used for the &quot;reference&quot; context, used to identify tiddlers that show up in the [[Reference]] list: 
&lt;&lt;option txtGTDReferenceContext&gt;&gt;

The following tag is used for the &quot;someday-maybe&quot; context, used to identify tiddlers that show up in the [[Someday-Maybe]] list:
&lt;&lt;option txtGTDSomedayContext&gt;&gt;

The following tag is used for the &quot;unfiled&quot; context, used to tag actions when the context is not known (such as orphaned actions from a deleted context):
&lt;&lt;option txtGTDUnfiledContext&gt;&gt;

!!Miscellaneous functionality
&lt;&lt;option chkGTDLazyAutoSave&gt;&gt; Use this checkbox to enable or disable &quot;lazy&quot; autosaving of changes to your document. If turned on, then the autosave will fire every &lt;&lt;option txtGTDLazyAutoSaveInterval&gt;&gt; seconds.


</pre>
</div>
<div title="CoreTweaks" modifier="ELSDesignStudios" created="200608201502" modified="200808050002" tags="systemConfig" creator="ELSDesignStudios">
<pre>/***
|Name|CoreTweaks|
|Source|http://www.TiddlyTools.com/#CoreTweaks|
|Version|n/a|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.2.4|
|Type|plugin|
|Requires||
|Overrides|various|
|Description|a small collection of overrides to TW core functions|
This tiddler contains some quick tweaks and modifications to TW core functions to provide minor changes in standard features or behavior.  It is hoped that some of these tweaks may be incorporated into later versions of the TW core, so that these adjustments will be available without needing these add-on definitions. ''Note: the changes contained in this tiddler are generally applicable for the current version of TiddlyWiki (&lt;&lt;version&gt;&gt;)./% Please view [[CoreTweaksArchive]] for tweaks and modifications that may be used with earlier versions of TiddlyWiki.%/''

To install //all// of these tweaks, import (or copy/paste) this tiddler into your document.  To include only //some// of the tweaks, you can edit the imported tiddler to remove the tweaks that you don't want.  Alternatively, you could copy/paste a few selected tweaks from this tiddler into a tiddler that you create in your own document.  Be certain to tag that tiddler with&lt;&lt;tag systemConfig&gt;&gt; (i.e., a plugin tiddler) and then save-and-reload for the tweaks to take effect.
***/
/***
!!! Ticketed Tweaks
***/
// // {{groupbox small{
/***
!!FireFox3 Import bug: &quot;browse&quot; button replacement
http://trac.tiddlywiki.org/ticket/683 - OPEN
see also http://trac.tiddlywiki.org/ticket/604 - cross-platform askForFilename()
The web standard &quot;type=file&quot; input control that has been used as a local path/file picker for TiddlyWiki no longer works as expected in FireFox3, which has, for &quot;security&quot; reasons, limited javascript access to this control so that *no* local filesystem path information can be revealed, even when it is intentional and necessary, as it is with TiddlyWiki.  This tweak provides alternative HTML source that patches the backstage import panel.  It replaces the &quot;type=file&quot; input control with a text+button combination of controls that invokes a system-native secure 'file-chooser' dialog box to provide TiddlyWiki with access to a complete path+filename so that TW functions properly locate user-selected local files.
***/
//{{{
if (window.Components) {
	var fixhtml='&lt;input name=&quot;txtBrowse&quot; style=&quot;width:30em&quot;&gt;&lt;input type=&quot;button&quot; value=&quot;...&quot;'
		+' onClick=&quot;window.browseForFilename(this.previousSibling,true)&quot;&gt;';
	var cmi=config.macros.importTiddlers;
	cmi.step1Html=cmi.step1Html.replace(/&lt;input type='file' size=50 name='txtBrowse'&gt;/,fixhtml);
}

merge(config.messages,{selectFile:&quot;Please enter or select a file&quot;}); // ready for I18N translation

window.browseForFilename=function(target,mustExist) { // note: both params are optional
	var msg=config.messages.selectFile;
	if (target &amp;&amp; target.title) msg=target.title; // use target field tooltip (if any) as dialog prompt text
	// get local path for current document
	var path=getLocalPath(document.location.href);
	var p=path.lastIndexOf(&quot;/&quot;); if (p==-1) p=path.lastIndexOf(&quot;\\&quot;); // Unix or Windows
	if (p!=-1) path=path.substr(0,p+1); // remove filename, leave trailing slash
	var file=&quot;&quot;
	var result=window.askForFilename(msg,path,file,mustExist); // requires #604
	if (target &amp;&amp; result.length) // set target field and trigger handling
		{ target.value=result; target.onchange(); }
	return result; 
}
//}}}
// // }}}
// // {{groupbox small{
/***
!!cross-platform askForFilename()
http://trac.tiddlywiki.org/ticket/604 - OPEN
invokes a system-native secure 'file-chooser' dialog box to provide TiddlyWiki with access to a complete path+filename so that TW functions properly locate user-selected local files.
***/
//{{{
window.askForFilename=function(msg,path,file,mustExist) {
	var r = window.mozAskForFilename(msg,path,file,mustExist);
	if(r===null || r===false)
		r = window.ieAskForFilename(msg,path,file,mustExist);
	if(r===null || r===false)
		r = window.javaAskForFilename(msg,path,file,mustExist);
	if(r===null || r===false)
		r = prompt(msg,path+file);
	return r||&quot;&quot;;
}

window.mozAskForFilename=function(msg,path,file,mustExist) {
	if(!window.Components) return false;
	try {
		netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
		var nsIFilePicker = window.Components.interfaces.nsIFilePicker;
		var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
		picker.init(window, msg, mustExist?nsIFilePicker.modeOpen:nsIFilePicker.modeSave);
		var thispath = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
		thispath.initWithPath(path);
		picker.displayDirectory=thispath;
		picker.defaultExtension='html';
		picker.defaultString=file;
		picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML);
		if (picker.show()!=nsIFilePicker.returnCancel)
			var result=picker.file.persistentDescriptor;
	}
	catch(ex) { displayMessage(ex.toString()); }
	return result;
}

window.ieAskForFilename=function(msg,path,file,mustExist) {
	if(!config.browser.isIE) return false;
	try {
		var s = new ActiveXObject('UserAccounts.CommonDialog');
		s.Filter='All files|*.*|Text files|*.txt|HTML files|*.htm;*.html|';
		s.FilterIndex=3; // default to HTML files;
		s.InitialDir=path;
		s.FileName=file;
		return s.showOpen()?s.FileName:&quot;&quot;;
	}
	catch(ex) { displayMessage(ex.toString()); }
	return result;
}

window.javaAskForFilename=function(msg,path,file,mustExist) {
	if(!document.applets[&quot;TiddlySaver&quot;]) return false;
	// TBD: implement java-based askFile(...) function
	try { return document.applets[&quot;TiddlySaver&quot;].askFile(msg,path,file,mustExist); } 
	catch(ex) { displayMessage(ex.toString()); }
}
//}}}
// // }}}
// // {{groupbox small{
/***
!!#story:... paramifier
http://trac.tiddlywiki.org/ticket/676 - OPEN
scan the specified 'story' tiddler content for embedded links, rather than simply parsing the content as a space-separated bracketed list.  This allows links from ''any'' tiddler to be used as a story, regardless of other wiki-syntax contained in that tiddler.  If specified tiddler is a shadow, fallback to using parseParams() to extract the list of links.
***/
//{{{
config.paramifiers.story = {
	onstart: function(v) {
		var t=store.getTiddler(v); if (t) t.changed();
		var list=t?t.links:store.getTiddlerText(v,&quot;&quot;).parseParams(&quot;open&quot;,null,false);
		story.displayTiddlers(null,list);
	}
};
//}}}
// // }}}
// // {{groupbox small{
/***
!!Loose links (case-folded/space-folded wiki words)
http://trac.tiddlywiki.org/ticket/664 - OPEN
This tweak matches non-WikiWord variations of mixed-case and/or added/omitted spaces within double-bracketed text with titles of //existing// tiddlers, using a &quot;loose&quot; (case-folded/space-folded) comparison.  This allows text that occurs in normal prose to be more easily linked to tiddler titles by using double-brackets without the full &quot;pretty link&quot; syntax.  For example:
{{{
[[CoreTweaks]], [[coreTweaks]], [[core tweaks]],
[[CORE TWEAKS]], [[CoRe TwEaKs]], [[coreTWEAKS]]
}}}
&gt;[[CoreTweaks]], [[coreTweaks]], [[core tweaks]],
&gt;[[CORE TWEAKS]], [[CoRe TwEaKs]], [[coreTWEAKS]]
Configuration:
&gt;&lt;&lt;option chkLooseLinks&gt;&gt; Allow case-folded and/or space-folded text to link to existing tiddler titles
&gt;{{{usage: &lt;&lt;option chkLooseLinks&gt;&gt;}}}
***/
//{{{
if (!config.options.chkLooseLinks)
	config.options.chkLooseLinks=false; // default to standard behavior
window.caseFold_createTiddlyLink = window.createTiddlyLink;
window.createTiddlyLink = function(place,title,includeText,className) {
	var btn=window.caseFold_createTiddlyLink.apply(this,arguments); // create core link
	if (!config.options.chkLooseLinks) return btn;
	if (store.getTiddlerText(title)) return btn; // matching tiddler (or shadow) exists
	var target=title.toLowerCase().replace(/\s/g,&quot;&quot;);
	var tids=store.getTiddlers(&quot;title&quot;);
	for (var t=0; t&lt;tids.length; t++) {
		if (tids[t].title.toLowerCase().replace(/\s/g,&quot;&quot;)==target) {
			var i=getTiddlyLinkInfo(tids[t].title,className);
			btn.setAttribute(&quot;tiddlyLink&quot;,tids[t].title);
			btn.title=i.subTitle;
			btn.className=i.classes;
			break;
		}
	}
	return btn;
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/657 - OPEN
// // This tweak inserts an extra space element following each tab, allowing them to wrap onto multiple lines if needed.
//{{{
config.macros.tabs.handler = function(place,macroName,params)
{
	var cookie = params[0];
	var numTabs = (params.length-1)/3;
	var wrapper = createTiddlyElement(null,&quot;div&quot;,null,&quot;tabsetWrapper &quot; + cookie);
	var tabset = createTiddlyElement(wrapper,&quot;div&quot;,null,&quot;tabset&quot;);
	tabset.setAttribute(&quot;cookie&quot;,cookie);
	var validTab = false;
	for(var t=0; t&lt;numTabs; t++) {
		var label = params[t*3+1];
		var prompt = params[t*3+2];
		var content = params[t*3+3];
		var tab = createTiddlyButton(tabset,label,prompt,this.onClickTab,&quot;tab tabUnselected&quot;);
		createTiddlyElement(tab,&quot;span&quot;,null,null,&quot; &quot;,{style:&quot;font-size:0pt;line-height:0px&quot;}); // ELS
		tab.setAttribute(&quot;tab&quot;,label);
		tab.setAttribute(&quot;content&quot;,content);
		tab.title = prompt;
		if(config.options[cookie] == label)
			validTab = true;
	}
	if(!validTab)
		config.options[cookie] = params[1];
	place.appendChild(wrapper);
	this.switchTab(tabset,config.options[cookie]);
};
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/637 - OPEN
// // This tweak modifies the tooltip format that appears when you mouseover a link to a tiddler.  It adds an option to control the date format, as well as displaying the size of the tiddler (in bytes)
// //
// // Tiddler link tooltip format:
// // {{stretch{&lt;&lt;option txtTiddlerLinkTootip&gt;&gt;}}}
// // ^^where: %0=title, %1=username, %2=modification date, %3=size in bytes^^
// // Tiddler link tooltip date format:
// // {{stretch{&lt;&lt;option txtTiddlerLinkTooltipDate&gt;&gt;}}}
//{{{
config.messages.tiddlerLinkTooltip=&quot;%0 - %1, %2 (%3 bytes)&quot;;
config.messages.tiddlerLinkTooltipDate=&quot;DDD, MMM DDth YYYY 0hh12:0mm AM&quot;;

config.options.txtTiddlerLinkTootip=
	config.options.txtTiddlerLinkTootip||config.messages.tiddlerLinkTooltip;
config.options.txtTiddlerLinkTooltipDate=
	config.options.txtTiddlerLinkTooltipDate||config.messages.tiddlerLinkTooltipDate;

Tiddler.prototype.getSubtitle = function() {
	var modifier = this.modifier;
	if(!modifier) modifier = config.messages.subtitleUnknown;
	var modified = this.modified;
	if(modified) modified = modified.formatString(config.options.txtTiddlerLinkTooltipDate);
	else modified = config.messages.subtitleUnknown;
	return config.options.txtTiddlerLinkTootip.format([this.title,modifier,modified,this.text.length]);
};
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/628 - OPEN
// // When invoking a macro that is not defined, this tweak prevents the display of the &quot;error in macro... no such macro&quot; message.  This is useful when rendering tiddler content or templates that reference macros that are defined by //optional// plugins that have not been installed in the current document.
// //
// // &lt;&lt;option chkHideMissingMacros&gt;&gt; hide &quot;no such macro&quot; error messages
//{{{
if (config.options.chkHideMissingMacros===undefined)
	config.options.chkHideMissingMacros=false;

window.coreTweaks_missingMacro_invokeMacro = window.invokeMacro;
window.invokeMacro = function(place,macro,params,wikifier,tiddler) {
	if (!config.macros[macro] || !config.macros[macro].handler)
		if (config.options.chkHideMissingMacros) return;
	window.coreTweaks_missingMacro_invokeMacro.apply(this,arguments);
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/609 - OPEN (separators)
// // http://trac.tiddlywiki.org/ticket/610 - OPEN (wikify tiddler/slice/section content)
// // These tweaks extend the {{{&lt;&lt;toolbar&gt;&gt;}}} macro to permit use of &quot;|&quot; as separators, as well as recognizing references to tiddlernames, slices, or sections and rendering their content inline within the toolbar
// // ''see [[ToolbarCommands]] for examples of how these features can be used''
//{{{
merge(config.macros.toolbar,{
	separator: &quot;|&quot;
	});
config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	for(var t=0; t&lt;params.length; t++) {
		var c = params[t];
		switch(c) {
			case '|':  // ELS - SEPARATOR
			case '!':  // ELS - SEPARATOR (alternative for use in tiddler slices)
				createTiddlyText(place,this.separator); // ELS
				break; // ELS
			case '&gt;':
				var btn = createTiddlyButton(place,this.moreLabel,this.morePrompt,config.macros.toolbar.onClickMore);
				addClass(btn,&quot;moreCommand&quot;);
				var e = createTiddlyElement(place,&quot;span&quot;,null,&quot;moreCommand&quot;);
				e.style.display = &quot;none&quot;;
				place = e;
				break;
			default:
				var theClass = &quot;&quot;;
				switch(c.substr(0,1)) {
					case &quot;+&quot;:
						theClass = &quot;defaultCommand&quot;;
						c = c.substr(1);
						break;
					case &quot;-&quot;:
						theClass = &quot;cancelCommand&quot;;
						c = c.substr(1);
						break;
				}
				if(c in config.commands)
					this.createCommand(place,c,tiddler,theClass);
				else { // ELS - WIKIFY TIDDLER/SLICE/SECTION
					if (c.substr(0,1)==&quot;~&quot;) c=c.substr(1); // ignore leading ~
					var txt=store.getTiddlerText(c);
					if (txt) {
						txt=txt.replace(/^\n*/,&quot;&quot;).replace(/\n*$/,&quot;&quot;); // trim any leading/trailing newlines
						txt=txt.replace(/^\{\{\{\n/,&quot;&quot;).replace(/\n\}\}\}$/,&quot;&quot;); // trim PRE format wrapper if any
						wikify(txt,createTiddlyElement(place,&quot;span&quot;),null,tiddler);
					}
				} // ELS - end WIKIFY CONTENT
				break;
		}
	}
};
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/608 - OPEN
// // This tweak extends the {{{&lt;&lt;toolbar&gt;&gt;}}} macro to make the &quot;&gt;&quot; (more) a //toggle// between more/less with the additional toolbar commands displayed on a separate line.
//{{{
merge(config.macros.toolbar,{
	moreLabel: 'more',
	morePrompt: &quot;Show additional commands&quot;,
	lessLabel: 'less',
	lessPrompt: &quot;Hide additional commands&quot;
});
config.macros.toolbar.onClickMore = function(ev)
{
	var e = this.nextSibling;
	var showing=e.style.display==&quot;block&quot;;
	e.style.display = showing?&quot;none&quot;:&quot;block&quot;;
	this.innerHTML=showing?config.macros.toolbar.moreLabel:config.macros.toolbar.lessLabel;
	this.title=showing?config.macros.toolbar.morePrompt:config.macros.toolbar.lessPrompt;
	return false;
};
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/607 - OPEN
// // This tweak automatically sets the HREF for the 'permaview' sidebar command link so you can use the 'right click' context menu for faster, easier bookmarking.  Note that this does ''not'' automatically set the permaview in the browser's current location URL... it just sets the HREF on the command link.  You still have to click the link to apply the permaview.
//{{{
config.macros.permaview.handler = function(place)
{
	var btn=createTiddlyButton(place,this.label,this.prompt,this.onClick);
	addEvent(btn,&quot;mouseover&quot;,this.setHREF);
	addEvent(btn,&quot;focus&quot;,this.setHREF);
};
config.macros.permaview.setHREF = function(event){
	var links = [];
	story.forEachTiddler(function(title,element) {
		links.push(String.encodeTiddlyLink(title));
	});
	var newURL=document.location.href;
	var hashPos=newURL.indexOf(&quot;#&quot;);
	if (hashPos!=-1) newURL=newURL.substr(0,hashPos);
	this.href=newURL+&quot;#&quot;+encodeURIComponent(links.join(&quot; &quot;));
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/529 - OPEN
// // This tweak hijacks the standard browser function, document.getElementById(), to work-around the case-INsensitivity error in Internet Explorer (all versions up to and including IE7) //''Note: This tweak is only applied when using IE, and only for lookups of rendered tiddler elements within the containing &quot;tiddlerDisplay&quot; element.''//
//{{{
if (config.browser.isIE) {
document.coreTweaks_coreGetElementById=document.getElementById;
document.getElementById=function(id) {
	var e=document.coreTweaks_coreGetElementById(id);
	if (!e || !e.parentNode || e.parentNode.id!=&quot;tiddlerDisplay&quot;) return e;
	for (var i=0; i&lt;e.parentNode.childNodes.length; i++)
		if (id==e.parentNode.childNodes[i].id) return e.parentNode.childNodes[i];
	return null;
};
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/471 - OPEN
// // This tweak HIJACKS the core's saveTiddler() function to automatically add a &quot;creator&quot; field to a tiddler when it is FIRST created. You can use {{{&lt;&lt;view creator&gt;&gt;}}} (or {{{&lt;&lt;view creator wikified&gt;&gt;}}} if you prefer) to show this value embedded directly within the tiddler content, or {{{&lt;span macro=&quot;view creator&quot;&gt;&lt;/span&gt;}}} in the ViewTemplate and/or EditTemplate to display the creator value in each tiddler.  
//{{{
// hijack saveTiddler()
TiddlyWiki.prototype.CoreTweaks_creatorSaveTiddler=TiddlyWiki.prototype.saveTiddler;
TiddlyWiki.prototype.saveTiddler=function(title,newTitle,newBody,modifier,modified,tags,fields)
{
	var existing=store.tiddlerExists(title);
	var tiddler=this.CoreTweaks_creatorSaveTiddler.apply(this,arguments);
	if (!existing) store.setValue(title,&quot;creator&quot;,config.options.txtUserName);
	return tiddler;
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/458 - CLOSED: WON'T FIX
// // This tweak assigns a &quot;permalink&quot;-like HREF to internal Tiddler links (which normally do not have any HREF defined).  This permits the link's context menu (right-click) to include 'open link in another window/tab' command.  Based on a request from Dustin Spicuzza.
//{{{
window.coreTweaks_createTiddlyLink=window.createTiddlyLink;
window.createTiddlyLink=function(place,title,includeText,theClass,isStatic,linkedFromTiddler,noToggle)
{
	// create the core button, then add the HREF (to internal links only)
	var link=window.coreTweaks_createTiddlyLink.apply(this,arguments);
	if (!isStatic)
		link.href=document.location.href.split(&quot;#&quot;)[0]+&quot;#&quot;+encodeURIComponent(String.encodeTiddlyLink(title));
	return link;
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/444 - OPEN
// // When invoking a macro, this tweak makes the current containing tiddler object and DOM rendering location available as global variables (window.tiddler and window.place, respectively).  These globals can then be used within &quot;computed macro parameters&quot; to retrieve tiddler-relative and/or DOM-relative values or perform tiddler-specific side-effect functionality.
//{{{
window.coreTweaks_invokeMacro = window.invokeMacro;
window.invokeMacro = function(place,macro,params,wikifier,tiddler) {
	var here=story.findContainingTiddler(place);
	window.tiddler=here?store.getTiddler(here.getAttribute(&quot;tiddler&quot;)):null;
	window.place=place;
	window.coreTweaks_invokeMacro.apply(this,arguments);
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/401 - CLOSED: WON'T FIX
// // This tweak allows definition of an optional [[PageTitle]] tiddler that, when present, provides alternative text for display in the browser window's titlebar, instead of using the combined text content from [[SiteTitle]] and [[SiteSubtitle]] (which will still be displayed as usual in the TiddlyWiki document header area)
//{{{
window.coreTweaks_getPageTitle=window.getPageTitle;
window.getPageTitle=function() { 
	var txt=wikifyPlain(&quot;PageTitle&quot;); if (txt.length) return txt;
	return window.coreTweaks_getPageTitle.apply(this,arguments);
}
store.addNotification(&quot;PageTitle&quot;,refreshPageTitle); // so title stays in sync with tiddler changes
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/67 - OPEN
// // The &quot;missing links&quot; list includes items contained within &quot;quoted&quot; text (i.e., content that will not render as wiki-syntax, and so CANNOT create any tiddler links, even if the quoted text matches valid link syntax).  This tweak removes content contained between certain delimiters before scanning tiddler source for possible links.
/***
Delimiters include:
{{{
/%...%/
{{{...}}}
&quot;&quot;&quot;...&quot;&quot;&quot;
&lt;nowiki&gt;...&lt;/nowiki&gt;
&lt;html&gt;...&lt;/html&gt;
&lt;script&gt;...&lt;/script&gt;
}}}
***/
//{{{
Tiddler.prototype.coreTweaks_changed = Tiddler.prototype.changed;
Tiddler.prototype.changed = function()
{
	var savedtext=this.text;
	// remove 'quoted' text before scanning tiddler source
	this.text=this.text.replace(/\/%((?:.|\n)*?)%\//g,&quot;&quot;); // /%...%/
	this.text=this.text.replace(/\{{3}((?:.|\n)*?)\}{3}/g,&quot;&quot;); // {{{...}}}
	this.text=this.text.replace(/&quot;{3}((?:.|\n)*?)&quot;{3}/g,&quot;&quot;); // &quot;&quot;&quot;...&quot;&quot;&quot;
	this.text=this.text.replace(/\&lt;nowiki\&gt;((?:.|\n)*?)\&lt;\/nowiki\&gt;/g,&quot;&quot;); // &lt;nowiki&gt;...&lt;/nowiki&gt;
	this.text=this.text.replace(/\&lt;html\&gt;((?:.|\n)*?)\&lt;\/html\&gt;/g,&quot;&quot;); // &lt;html&gt;...&lt;/html&gt;
	this.text=this.text.replace(/\&lt;script((?:.|\n)*?)\&lt;\/script\&gt;/g,&quot;&quot;); // &lt;script&gt;...&lt;/script&gt;
	this.coreTweaks_changed.apply(this,arguments);
	// restore quoted text to tiddler source
	this.text=savedtext;
};
//}}}
// // }}}
/***
!!! Fixed in current release (TW&lt;&lt;version&gt;&gt;)
***/
// // {{groupbox small{
// // calculate version number for conditional inclusion of tweaks below...
//{{{
var ver=version.major+version.minor/10+version.revision/100;
//}}}
// // }}}
// // {{groupbox small{
/***
!!#filter:&quot;...&quot; paramifier
http://trac.tiddlywiki.org/ticket/678 - FIXED IN TW241
displays all tiddlers that match any filter criteria (including tag-matching), using the {{{store.filterTiddlers()}}} syntax.  Note use of double-quotes to enclose value to ensure that square-brackets within filter syntax are passed through for processing.
***/
//{{{
if (ver&lt;2.41) {
config.paramifiers.filter = {
	onstart: function(v) {
		var tagged = store.filterTiddlers(v);
		story.displayTiddlers(null,tagged,null,false,null);
	}
};
}
//}}}
// // }}}
// // {{groupbox small{
/***
!!#tag:... paramifier
http://trac.tiddlywiki.org/ticket/677 - FIXED IN TW241
use {{{store.filterTiddlers()}}} instead of {{{store.getTaggedTiddlers()}}}.  This permits enhanced tag-matching logic (such as boolean expression processing provided by [[MatchTagsPlugin]]) to be used, e.g., {{{#tag:&quot;tag1 OR (tag2 AND NOT tag3)&quot;}}}, instead of simply matching a single tag value.
***/
//{{{
if (ver&lt;2.41) {
config.paramifiers.tag = {
	onstart: function(v) {
		var tagged = store.filterTiddlers(&quot;[tag[&quot;+v+&quot;]]&quot;);
		story.displayTiddlers(null,tagged,null,false,null);
	}
};
}
//}}}
// // }}}
// // {{groupbox small{
/***
!!#recent:... paramifier
http://trac.tiddlywiki.org/ticket/675 - FIXED IN TW241
automatically display the N most recently changed tiddlers.  N is, of course, an integer number.  If N=0 (or is not a numeric value), the regular [[DefaultTiddlers]] will be displayed.
***/
//{{{
if (ver&lt;2.41) {
config.paramifiers.recent= {
	onstart: function(v) {
		var titles=[];
		var tids=store.getTiddlers(&quot;modified&quot;,&quot;excludeLists&quot;).reverse();
		for (var t=0; t&lt;v &amp;&amp; t&lt;tids.length; t++) titles.push(tids[t].title);
		story.displayTiddlers(null,titles); 
	}
};
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/635 - FIXED IN TW241
// // When using backstage&gt;import &quot;browse&quot; button, resulting URL is improperly formed with &quot;file://&quot; prefix instead of &quot;file:///&quot; prefix.  This causes errors when using Firefox 3 (beta) or when running under Windows Vista OS.
// // http://trac.tiddlywiki.org/ticket/638 - FIXED IN TW241
// // When entering text directly into path/file field, each keystroke is displayed and then discarded, preventing manual entry of path/file.
// // http://trac.tiddlywiki.org/ticket/639 - FIXED IN TW241
// // Pressing &quot;enter&quot; from URL or Browse input field immediately reloads the current document.  Instead, it should trigger the &quot;open&quot; button for the import wizard (if a URL has been entered)
//{{{
if (ver&lt;2.41) {
// #635 and #638
config.macros.importTiddlers.onBrowseChange = function(e)
{
	var wizard = new Wizard(this);
	var fileInput = wizard.getElement(&quot;txtPath&quot;);
	fileInput.value = config.macros.importTiddlers.getURLFromLocalPath(this.value); // #635
	var serverType = wizard.getElement(&quot;selTypes&quot;);
	serverType.value = &quot;file&quot;;
	return true; // #638
};
// #635 - fixup local path/file to form absolute URL reference
config.macros.importTiddlers.getURLFromLocalPath = function(v)
{
	if (!v||!v.length) return v;
	v=v.replace(/\\/g,&quot;/&quot;); // use &quot;/&quot; for cross-platform consistency
	var t=v.split(&quot;:&quot;); p=t[1]||t[0]; // remove drive letter (if any)
	if (t[1] &amp;&amp; (t[0]==&quot;http&quot;||t[0]==&quot;https&quot;||t[0]==&quot;file&quot;)) { // input is already a URL
		var u=v;
	} else if (p.substr(0,1)==&quot;/&quot;) { // path is absolute, add protocol+domain+extra slash (if drive letter)
		var u=document.location.protocol+&quot;//&quot;+document.location.hostname+(t[1]?&quot;/&quot;:&quot;&quot;)+v;
	} else { // path is relative, add current document protocol+domain+path
		var c=document.location.href.replace(/\\/g,&quot;/&quot;);
		var pos=c.lastIndexOf(&quot;/&quot;); if (pos!=-1) c=c.substr(0,pos); // remove filename
		var u=c+&quot;/&quot;+p;
	}
	return u;
}
// #639 - prevent form action and click &quot;open&quot; button if ENTER is pressed
config.macros.importTiddlers.coreTweaks_restart = config.macros.importTiddlers.restart;
config.macros.importTiddlers.restart = function(wizard)
{
	config.macros.importTiddlers.coreTweaks_restart.apply(this,arguments);
	wizard.formElem.action=&quot;javascript:;&quot;
	wizard.formElem.onsubmit=function() {
		if (this.txtPath.value.length)
			this.lastChild.firstChild.onclick();  // press &quot;open&quot; button
	}
};
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/623 - FIXED IN TW241
/***
This tweak allows date format strings to contain backslash-quoted characters that bypass date format replacement.  This allows sequences such as &quot;s\s&quot;, &quot;m\m&quot; or &quot;a\m&quot; to be used so that &quot;ss&quot;, &quot;mm&quot; or &quot;am&quot; can appears as literal text within journal titles or other date-formatted values.

For example:
&gt;{{{&lt;&lt;today &quot;withhold less hummingbirds - YYYY.0MM.0DD 0hh:0mm:0ss&quot;&gt;&gt;}}}
&gt;results in: &lt;&lt;today &quot;withhold less hummingbirds - YYYY.0MM.0DD 0hh:0mm:0ss&quot;&gt;&gt;
while:
&gt;{{{&lt;&lt;today &quot;with\hold les\s hum\mingbirds - YYYY.0MM.0DD 0hh:0mm:0ss&quot;&gt;&gt;}}}
&gt;results in: &lt;&lt;today &quot;with\hold les\s hum\mingbirds - YYYY.0MM.0DD 0hh:0mm:0ss&quot;&gt;&gt;
***/
//{{{
if (ver&lt;2.41) {
Date.prototype.coreTweaks_formatString = Date.prototype.formatString;
Date.prototype.formatString = function(template) {
	var t = Date.prototype.coreTweaks_formatString.apply(this,arguments);
	t = t.replace(/\\/g,&quot;&quot;); // strip backslashes used to quote formats
	return t;
};
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/578 - FIXED IN TW240
// // This tweak trims any leading whitespace/newline and the trailing newline from tiddler sections
//{{{
if (ver&lt;2.4) {
TiddlyWiki.prototype.coreTweaks_getTiddlerText = TiddlyWiki.prototype.getTiddlerText;
TiddlyWiki.prototype.getTiddlerText = function(title,defaultText)
{
	var r=TiddlyWiki.prototype.coreTweaks_getTiddlerText.apply(this,arguments);
	if (r&amp;&amp;title.indexOf(config.textPrimitives.sectionSeparator)!=-1)
		r=r.replace(/^[ \t]*\n/,&quot;&quot;).replace(/\n$/,&quot;&quot;); // trim any leading/trailing newlines
	return r;
};
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/541 - FIXED IN TW240
// // This tweak adds a conditional check to the core's 'open' paramifier, so that when the document is viewed in readOnly mode, non-existent tiddlers specified using a permalink/permaview (i.e. &quot;#TiddlerName&quot; in the document URL) will not be displayed as an empty tiddler (which shows the &quot;double-click to create&quot; default text).
//{{{
if (ver&lt;2.4) {
config.paramifiers.open = { 
onstart: function(v) { 
		if(!readOnly || store.tiddlerExists(v) || store.isShadowTiddler(v)) 
			story.displayTiddler(&quot;bottom&quot;,v,null,false,null); 
	} 
}; 
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/470 - FIXED IN TW240
// // This tweak lets you set an alternative initial focus field when editing a tiddler (default field is &quot;text&quot;)
// // Enter initial focus field name: &lt;&lt;option txtEditorFocus&gt;&gt; (//usage:// {{{&lt;&lt;option txtEditorFocus&gt;&gt;}}})
//{{{
if (ver&lt;2.4) {
config.commands.editTiddler.coreTweaks_handler = config.commands.editTiddler.handler;
config.commands.editTiddler.handler = function(event,src,title)
{
	if (config.options.txtEditorFocus==undefined) config.options.txtEditorFocus=&quot;text&quot;;
	this.coreTweaks_handler.apply(this,arguments);
	story.focusTiddler(title,config.options.txtEditorFocus);
	return false;
};
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/468 - FIXED IN TW240
// // This tweak extends the core's {{{&lt;&lt;tag&gt;&gt;}}} macro to accept additional parameters for specifying alternative label and tooltip text for the tag popup &quot;button&quot; link (i.e., &quot;`PrettyTags&quot;).  Based on a suggestion by ~PBee.
//{{{
// hijack tag handler()
if (ver&lt;2.4) {
config.macros.tag.CoreTweaks_handler=config.macros.tag.handler;
config.macros.tag.handler = function(place,macroName,params)
{
	this.CoreTweaks_handler.apply(this,arguments);
	var btn=place.lastChild;
	if (params[1]) btn.innerHTML=params[1];
	if (params[2]) btn.title=params[2];
}
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/320 - FIXED IN TW240
// // This tweak updates the core's forceReflow() function to fix a Firefox rendering problem, whereby the contents of the a tiddler editor text area can be incorrectly displayed (overlapping other content) when more than one tiddler is in edit mode.
//{{{
if (ver&lt;2.4) {
function forceReflow()
{
	if(config.browser.isGecko) {
		setStylesheet(&quot;body {top:-0px;margin-top:0px;}&quot;);
		setTimeout('setStylesheet(&quot;&quot;)',1); // invoke async to bypass browser optimization
	}
}
}
//}}}
// // }}}
// // {{groupbox small{
// // http://trac.tiddlywiki.org/ticket/42 - FIXED IN TW240
// // This tweak adjusts the left position of a TW popup so that it won't overlap with the browser window's vertical scrollbar, when present.
//{{{
if (ver&lt;2.4) {
Popup.place = function(root,popup,offset)
{
	if(!offset) var offset = {x:0, y:0};
	var rootLeft = findPosX(root);
	var rootTop = findPosY(root);
	var rootHeight = root.offsetHeight;
	var popupLeft = rootLeft + offset.x;
	var popupTop = rootTop + rootHeight + offset.y;
	var winWidth = findWindowWidth();
	if(popup.offsetWidth &gt; winWidth*0.75)
		popup.style.width = winWidth*0.75 + &quot;px&quot;;
	var popupWidth = popup.offsetWidth;
	// ELS: leave space for vertical scrollbar
	var scrollWidth=winWidth-document.body.offsetWidth;
	if(popupLeft+popupWidth &gt; winWidth-scrollWidth-1)
		popupLeft = winWidth-popupWidth-scrollWidth-1;
	popup.style.left = popupLeft + &quot;px&quot;;
	popup.style.top = popupTop + &quot;px&quot;;
	popup.style.display = &quot;block&quot;;
};
}
//}}}
// // }}}
/***
!!!Unticketed Tweaks
***/
// // {{groupbox small{
/***
!!#animate:... paramifier
http://trac.tiddlywiki.org/ticket/TBD - TBD
This tweak provides a paramifier to turn on/off animation
***/
//{{{
config.paramifiers.animate= {
	onstart: function(v) {
		config.options.chkAnimate=eval(v);
	}
};
//}}}
// // }}}
// // {{groupbox small{
// // This tweak adds an optional 'sortby' parameter to the {{{&lt;&lt;tag tagname label tip sortby&gt;&gt;}}} macro, as well as the {{{&lt;&lt;allTags excludeTag sortby&gt;&gt;}}} macro used to generate the sidebar contents 'tags' list.  Specify the field on which the contents of each tag popup is to be sorted, with a &quot;+&quot; or &quot;-&quot; prefix to indicate ascending/descending order, respectively.

// // Example: {{{&lt;&lt;tag systemConfig &quot;plugins&quot; &quot;list plugins by date, most recent first&quot; &quot;-modified&quot;&gt;&gt;}}}
// // Try it: &lt;&lt;tag systemConfig &quot;plugins&quot; &quot;list plugins by date, most recent first&quot; &quot;-modified&quot;&gt;&gt;

// // Similarly, to change the sort order used by the popups from all tags shown in the sidebar contents, edit the [[TagTags]] shadow tiddler and enter: {{{&lt;&lt;allTags excludeLists -modified&gt;&gt;}}}

//{{{
// hijack tag handler() to add 'sortby' attribute to tag button
config.macros.tag.CoreTweaksSortTags_handler=config.macros.tag.handler;
config.macros.tag.handler = function(place,macroName,params)
{
	this.CoreTweaksSortTags_handler.apply(this,arguments);
	var btn=place.lastChild;
	if (params[3]) btn.setAttribute(&quot;sortby&quot;,params[3]);
}

// TWEAK &lt;&lt;allTags&gt;&gt; macro to add 'sortby' attribute to each tag button
var fn=config.macros.allTags.handler;
var lines=fn.toString().split(&quot;\n&quot;);
lines.splice(lines.length-2,0,['if(params[1]) btn.setAttribute(&quot;sortby&quot;,params[1]);']);
fn=lines.join(&quot;\n&quot;);
eval(&quot;config.macros.allTags.handler=&quot;+fn);

// TWEAK event handler for clicking on a tiddler tag to use 'sortby' attribute
var fn=onClickTag;
fn=fn.toString().replace(
	/store.getTaggedTiddlers\(tag\);/g,
	'store.getTaggedTiddlers(tag);'
	+'var sortby=this.getAttribute(&quot;sortby&quot;);'
	+'if(sortby&amp;&amp;sortby.length) store.sortTiddlers(tagged,sortby);'
);
eval(fn);

//}}}
// // }}}
// // {{groupbox small{
// // This HIJACK tweak pre-processes source content to convert &quot;double-backslash-newline&quot; into {{{&lt;br&gt;}}} before wikify(), so that literal newlines can be embedded in line-mode wiki syntax (e.g., tables, bullets, etc.).  Based on a suggestion from Sitaram Chamarty.
//{{{
window.coreWikify = wikify;
window.wikify = function(source,output,highlightRegExp,tiddler)
{
	if (source) arguments[0]=source.replace(/\\\\\n/mg,&quot;&lt;br&gt;&quot;);
	coreWikify.apply(this,arguments);
}
//}}}
// // }}}</pre>
</div>
<div title="DatePlugin" modifier="ELSDesignStudios" created="200512271859" modified="200803082100" tags="systemConfig" creator="ELSDesignStudios">
<pre>/***
|Name|DatePlugin|
|Source|http://www.TiddlyTools.com/#DatePlugin|
|Documentation|http://www.TiddlyTools.com/#DatePluginInfo|
|Version|2.7.0|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1|
|Type|plugin|
|Requires||
|Overrides||
|Options|##Configuration|
|Description|formatted dates plus popup menu with 'journal' link, changes and (optional) reminders|
There are quite a few calendar generators, reminders, to-do lists, 'dated tiddlers' journals, blog-makers and GTD-like schedule managers that have been built around TW.  While they all have different purposes, and vary in format, interaction, and style, in one way or another each of these plugins displays and/or uses date-based information to make finding, accessing and managing relevant tiddlers easier.  This plugin provides a general approach to embedding dates and date-based links/menus within tiddler content.
!!!!!Documentation
&gt;see [[DatePluginInfo]]
!!!!!Configuration
&lt;&lt;&lt;
&lt;&lt;option chkDatePopupHideCreated&gt;&gt; omit 'created' section from date popups
&lt;&lt;option chkDatePopupHideChanged&gt;&gt; omit 'changed' section from date popups
&lt;&lt;option chkDatePopupHideTagged&gt;&gt; omit 'tagged' section from date popups
&lt;&lt;option chkDatePopupHideReminders&gt;&gt; omit 'reminders' section from date popups
&lt;&lt;option chkShowJulianDate&gt;&gt; display Julian day number (1-365) below current date

see [[DatePluginConfig]] for additional configuration settings, for use in calendar displays, including:
*date formats
*color-coded backgrounds
*annual fixed-date holidays
*weekends
&lt;&lt;&lt;
!!!!!Revisions
&lt;&lt;&lt;
2008.03.08 [2.7.0] in addModifiedsToPopup(), if a tiddler was created on the specified date, don't list it in the 'changed' section of the popup.  Based on a request from Kashgarinn.
|please see [[DatePluginInfo]] for additional revision details|
2005.10.30 [0.9.0] pre-release
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.date = {major: 2, minor: 7, revision: 0, date: new Date(2008,3,8)};

config.macros.date = {
	format: &quot;YYYY.0MM.0DD&quot;, // default date display format
	linkformat: &quot;YYYY.0MM.0DD&quot;, // 'dated tiddler' link format
	linkedbg: &quot;#babb1e&quot;, // &quot;babble&quot;
	todaybg: &quot;#ffab1e&quot;, // &quot;fable&quot;
	weekendbg: &quot;#c0c0c0&quot;, // &quot;cocoa&quot;
	holidaybg: &quot;#ffaace&quot;, // &quot;face&quot;
	createdbg: &quot;#bbeeff&quot;, // &quot;beef&quot;
	modifiedsbg: &quot;#bbeeff&quot;, // &quot;beef&quot;
	remindersbg: &quot;#c0ffee&quot;, // &quot;coffee&quot;
	holidays: [ &quot;01/01&quot;, &quot;07/04&quot;, &quot;07/24&quot;, &quot;11/24&quot; ], // NewYearsDay, IndependenceDay(US), Eric's Birthday (hooray!), Thanksgiving(US)
	weekend: [ 1,0,0,0,0,0,1 ] // [ day index values: sun=0, mon=1, tue=2, wed=3, thu=4, fri=5, sat=6 ]
};

config.macros.date.handler = function(place,macroName,params)
{
	// do we want to see a link, a popup, or just a formatted date?
	var mode=&quot;display&quot;;
	if (params[0]==&quot;display&quot;) { mode=params[0]; params.shift(); }
	if (params[0]==&quot;popup&quot;) { mode=params[0]; params.shift(); }
	if (params[0]==&quot;link&quot;) { mode=params[0]; params.shift(); }
	// get the date
	var now = new Date();
	var date = now;
	if (!params[0] || params[0]==&quot;today&quot;)
		{ params.shift(); }
	else if (params[0]==&quot;filedate&quot;)
		{ date=new Date(document.lastModified); params.shift(); }
	else if (params[0]==&quot;tiddler&quot;)
		{ date=store.getTiddler(story.findContainingTiddler(place).id.substr(7)).modified; params.shift(); }
	else if (params[0].substr(0,8)==&quot;tiddler:&quot;)
		{ var t; if ((t=store.getTiddler(params[0].substr(8)))) date=t.modified; params.shift(); }
	else {
		var y = eval(params.shift().replace(/Y/ig,(now.getYear()&lt;1900)?now.getYear()+1900:now.getYear()));
		var m = eval(params.shift().replace(/M/ig,now.getMonth()+1));
		var d = eval(params.shift().replace(/D/ig,now.getDate()+0));
		date = new Date(y,m-1,d);
	}
	// date format with optional custom override
	var format=this.format; if (params[0]) format=params.shift();
	var linkformat=this.linkformat; if (params[0]) linkformat=params.shift();
	showDate(place,date,mode,format,linkformat);
}

window.showDate=showDate;
function showDate(place,date,mode,format,linkformat,autostyle,weekend)
{
	if (!mode) mode=&quot;display&quot;;
	if (!format) format=config.macros.date.format;
	if (!linkformat) linkformat=config.macros.date.linkformat;
	if (!autostyle) autostyle=false;

	// format the date output
	var title = date.formatString(format);
	var linkto = date.formatString(linkformat);

	// just show the formatted output
	if (mode==&quot;display&quot;) { place.appendChild(document.createTextNode(title)); return; }

	// link to a 'dated tiddler'
	var link = createTiddlyLink(place, linkto, false);
	link.appendChild(document.createTextNode(title));
	link.title = linkto;
	link.date = date;
	link.format = format;
	link.linkformat = linkformat;

	// if using a popup menu, replace click handler for dated tiddler link
	// with handler for popup and make link text non-italic (i.e., an 'existing link' look)
	if (mode==&quot;popup&quot;) {
		link.onclick = onClickDatePopup;
		link.style.fontStyle=&quot;normal&quot;;
	}
	// format the popup link to show what kind of info it contains (for use with calendar generators)
	if (autostyle) setDateStyle(place,link,weekend);
}
//}}}

//{{{
// NOTE: This function provides default logic for setting the date style when displayed in a calendar
// To customize the date style logic, please see[[DatePluginConfig]]
function setDateStyle(place,link,weekend) {
	// alias variable names for code readability
	var date=link.date;
	var fmt=link.linkformat;
	var linkto=date.formatString(fmt);
	var cmd=config.macros.date;

	if ((weekend!==undefined?weekend:isWeekend(date))&amp;&amp;(cmd.weekendbg!=&quot;&quot;))
		{ place.style.background = cmd.weekendbg; }
	if (hasModifieds(date)||hasCreateds(date)||hasTagged(date,fmt))
		{ link.style.fontStyle=&quot;normal&quot;; link.style.fontWeight=&quot;bold&quot;; }
	if (hasReminders(date))
		{ link.style.textDecoration=&quot;underline&quot;; }
	if (isToday(date))
		{ link.style.border=&quot;1px solid black&quot;; }
	if (isHoliday(date)&amp;&amp;(cmd.holidaybg!=&quot;&quot;))
		{ place.style.background = cmd.holidaybg; }
	if (hasCreateds(date)&amp;&amp;(cmd.createdbg!=&quot;&quot;))
		{ place.style.background = cmd.createdbg; }
	if (hasModifieds(date)&amp;&amp;(cmd.modifiedsbg!=&quot;&quot;))
		{ place.style.background = cmd.modifiedsbg; }
	if ((hasTagged(date,fmt)||store.tiddlerExists(linkto))&amp;&amp;(cmd.linkedbg!=&quot;&quot;))
		{ place.style.background = cmd.linkedbg; }
	if (hasReminders(date)&amp;&amp;(cmd.remindersbg!=&quot;&quot;))
		{ place.style.background = cmd.remindersbg; }
	if (isToday(date)&amp;&amp;(cmd.todaybg!=&quot;&quot;))
		{ place.style.background = cmd.todaybg; }
	if (config.options.chkShowJulianDate) { // optional display of Julian date numbers
		var m=[0,31,59,90,120,151,181,212,243,273,304,334];
		var d=date.getDate()+m[date.getMonth()];
		var y=date.getFullYear();
		if (date.getMonth()&gt;1 &amp;&amp; (y%4==0 &amp;&amp; y%100!=0) || y%400==0)
			d++; // after February in a leap year
		wikify(&quot;@@font-size:80%;&lt;br&gt;&quot;+d+&quot;@@&quot;,place);
	}

}
//}}}

//{{{
function isToday(date) // returns true if date is today
	{ var now=new Date(); return ((now-date&gt;=0) &amp;&amp; (now-date&lt;86400000)); }

function isWeekend(date) // returns true if date is a weekend
	{ return (config.macros.date.weekend[date.getDay()]); }

function isHoliday(date) // returns true if date is a holiday
{
	var longHoliday = date.formatString(&quot;0MM/0DD/YYYY&quot;);
	var shortHoliday = date.formatString(&quot;0MM/0DD&quot;);
	for(var i = 0; i &lt; config.macros.date.holidays.length; i++) {
		var holiday=config.macros.date.holidays[i];
		if (holiday==longHoliday||holiday==shortHoliday) return true;
	}
	return false;
}
//}}}

//{{{
// Event handler for clicking on a day popup
function onClickDatePopup(e)
{
	if (!e) var e = window.event;
	var theTarget = resolveTarget(e);
	var popup = Popup.create(this);
	if(popup) {
		// always show dated tiddler link (or just date, if readOnly) at the top...
		if (!readOnly || store.tiddlerExists(this.date.formatString(this.linkformat)))
			createTiddlyLink(popup,this.date.formatString(this.linkformat),true);
		else
			createTiddlyText(popup,this.date.formatString(this.linkformat));
		if (!config.options.chkDatePopupHideCreated)
			addCreatedsToPopup(popup,this.date,this.format);
		if (!config.options.chkDatePopupHideChanged)
			addModifiedsToPopup(popup,this.date,this.format);
		if (!config.options.chkDatePopupHideTagged)
			addTaggedToPopup(popup,this.date,this.linkformat);
		if (!config.options.chkDatePopupHideReminders)
			addRemindersToPopup(popup,this.date,this.linkformat);
	}
	Popup.show(popup,false);
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
	return(false);
}
//}}}

//{{{
function indexCreateds() // build list of tiddlers, hash indexed by creation date
{
	var createds= { };
	var tiddlers = store.getTiddlers(&quot;title&quot;,&quot;excludeLists&quot;);
	for (var t = 0; t &lt; tiddlers.length; t++) {
		var date = tiddlers[t].created.formatString(&quot;YYYY0MM0DD&quot;)
		if (!createds[date])
			createds[date]=new Array();
		createds[date].push(tiddlers[t].title);
	}
	return createds;
}
function hasCreateds(date) // returns true if date has created tiddlers
{
	if (!config.macros.date.createds) config.macros.date.createds=indexCreateds();
	return (config.macros.date.createds[date.formatString(&quot;YYYY0MM0DD&quot;)]!=undefined);
}

function addCreatedsToPopup(popup,when,format)
{
	var force=(store.isDirty() &amp;&amp; when.formatString(&quot;YYYY0MM0DD&quot;)==new Date().formatString(&quot;YYYY0MM0DD&quot;));
	if (force || !config.macros.date.createds) config.macros.date.createds=indexCreateds();
	var indent=String.fromCharCode(160)+String.fromCharCode(160);
	var createds = config.macros.date.createds[when.formatString(&quot;YYYY0MM0DD&quot;)];
	if (createds) {
		createds.sort();
		var e=createTiddlyElement(popup,&quot;div&quot;,null,null,&quot;created (&quot;+createds.length+&quot;)&quot;);
		for(var t=0; t&lt;createds.length; t++) {
			var link=createTiddlyLink(popup,createds[t],false);
			link.appendChild(document.createTextNode(indent+createds[t]));
			createTiddlyElement(popup,&quot;br&quot;,null,null,null);
		}
	}
}
//}}}

//{{{
function indexModifieds() // build list of tiddlers, hash indexed by modification date
{
	var modifieds= { };
	var tiddlers = store.getTiddlers(&quot;title&quot;,&quot;excludeLists&quot;);
	for (var t = 0; t &lt; tiddlers.length; t++) {
		var date = tiddlers[t].modified.formatString(&quot;YYYY0MM0DD&quot;)
		if (!modifieds[date])
			modifieds[date]=new Array();
		modifieds[date].push(tiddlers[t].title);
	}
	return modifieds;
}
function hasModifieds(date) // returns true if date has modified tiddlers
{
	if (!config.macros.date.modifieds) config.macros.date.modifieds = indexModifieds();
	return (config.macros.date.modifieds[date.formatString(&quot;YYYY0MM0DD&quot;)]!=undefined);
}

function addModifiedsToPopup(popup,when,format)
{
	var date=when.formatString(&quot;YYYY0MM0DD&quot;);
	var force=(store.isDirty() &amp;&amp; date==new Date().formatString(&quot;YYYY0MM0DD&quot;));
	if (force || !config.macros.date.modifieds) config.macros.date.modifieds=indexModifieds();
	var indent=String.fromCharCode(160)+String.fromCharCode(160);
	var mods = config.macros.date.modifieds[date];
	if (mods) {
		// if a tiddler was created on this date, don't list it in the 'changed' section
		if (config.macros.date.createds &amp;&amp; config.macros.date.createds[date]) {
			var temp=[];
			for(var t=0; t&lt;mods.length; t++)
				if (!config.macros.date.createds[date].contains(mods[t]))
					temp.push(mods[t]);
			mods=temp;
		}
		mods.sort();
		var e=createTiddlyElement(popup,&quot;div&quot;,null,null,&quot;changed (&quot;+mods.length+&quot;)&quot;);
		for(var t=0; t&lt;mods.length; t++) {
			var link=createTiddlyLink(popup,mods[t],false);
			link.appendChild(document.createTextNode(indent+mods[t]));
			createTiddlyElement(popup,&quot;br&quot;,null,null,null);
		}
	}
}
//}}}

//{{{
function hasTagged(date,format) // returns true if date is tagging other tiddlers
{
	return store.getTaggedTiddlers(date.formatString(format)).length&gt;0;
}

function addTaggedToPopup(popup,when,format)
{
	var indent=String.fromCharCode(160)+String.fromCharCode(160);
	var tagged=store.getTaggedTiddlers(when.formatString(format));
	if (tagged.length) var e=createTiddlyElement(popup,&quot;div&quot;,null,null,&quot;tagged (&quot;+tagged.length+&quot;)&quot;);
	for(var t=0; t&lt;tagged.length; t++) {
		var link=createTiddlyLink(popup,tagged[t].title,false);
		link.appendChild(document.createTextNode(indent+tagged[t].title));
		createTiddlyElement(popup,&quot;br&quot;,null,null,null);
	}
}
//}}}

//{{{
function indexReminders(date,leadtime) // build list of tiddlers with reminders, hash indexed by reminder date
{
	var reminders = { };
	if(window.findTiddlersWithReminders!=undefined) { // reminder plugin is installed
		// DEBUG var starttime=new Date();
		var t = findTiddlersWithReminders(date, [0,leadtime], null, null, 1);
		for(var i=0; i&lt;t.length; i++) reminders[t[i].matchedDate]=true;
		// DEBUG var out=&quot;Found &quot;+t.length+&quot; reminders in &quot;+((new Date())-starttime+1)+&quot;ms\n&quot;;
		// DEBUG out+=&quot;startdate: &quot;+date.toLocaleDateString()+&quot;\n&quot;+&quot;leadtime: &quot;+leadtime+&quot; days\n\n&quot;;
		// DEBUG for(var i=0; i&lt;t.length; i++) { out+=t[i].matchedDate.toLocaleDateString()+&quot; &quot;+t[i].params.title+&quot;\n&quot;; }
		// DEBUG alert(out);
	}
	return reminders;
}

function hasReminders(date) // returns true if date has reminders
{
	if (window.reminderCacheForCalendar)
		return window.reminderCacheForCalendar[date]; // use calendar cache
	if (!config.macros.date.reminders)
		config.macros.date.reminders = indexReminders(date,90); // create a 90-day leadtime reminder cache
	return (config.macros.date.reminders[date]);
}

function addRemindersToPopup(popup,when,format)
{
	if(window.findTiddlersWithReminders==undefined) return; // reminder plugin not installed

	var indent = String.fromCharCode(160)+String.fromCharCode(160);
	var reminders=findTiddlersWithReminders(when, [0,31],null,null,1);
	createTiddlyElement(popup,&quot;div&quot;,null,null,&quot;reminders (&quot;+(reminders.length||&quot;none&quot;)+&quot;)&quot;);
	for(var t=0; t&lt;reminders.length; t++) {
		link = createTiddlyLink(popup,reminders[t].tiddler,false);
		var diff=reminders[t].diff;
		diff=(diff&lt;1)?&quot;Today&quot;:((diff==1)?&quot;Tomorrow&quot;:diff+&quot; days&quot;);
		var txt=(reminders[t].params[&quot;title&quot;])?reminders[t].params[&quot;title&quot;]:reminders[t].tiddler;
		link.appendChild(document.createTextNode(indent+diff+&quot; - &quot;+txt));
		createTiddlyElement(popup,&quot;br&quot;,null,null,null);
	}
	if (readOnly) return;	// omit &quot;new reminder...&quot; link
	var link = createTiddlyLink(popup,indent+&quot;new reminder...&quot;,true); createTiddlyElement(popup,&quot;br&quot;);
	var title = when.formatString(format);
	link.title=&quot;add a reminder to '&quot;+title+&quot;'&quot;;
	link.onclick = function() {
		// show tiddler editor
		story.displayTiddler(null, title, 2, null, null, false, false);
		// find body 'textarea'
		var c =document.getElementById(&quot;tiddler&quot; + title).getElementsByTagName(&quot;*&quot;);
		for (var i=0; i&lt;c.length; i++) if ((c[i].tagName.toLowerCase()==&quot;textarea&quot;) &amp;&amp; (c[i].getAttribute(&quot;edit&quot;)==&quot;text&quot;)) break;
		// append reminder macro to tiddler content
		if (i&lt;c.length) {
			if (store.tiddlerExists(title)) c[i].value+=&quot;\n&quot;; else c[i].value=&quot;&quot;;
			c[i].value += &quot;&lt;&lt;reminder&quot;;
			c[i].value += &quot; day:&quot;+when.getDate();
			c[i].value += &quot; month:&quot;+(when.getMonth()+1);
			c[i].value += &quot; year:&quot;+when.getFullYear();
			c[i].value += ' title:&quot;Enter a title&quot; &gt;&gt;';
		}
	};
}
//}}}</pre>
</div>
<div title="DatePluginConfig" modifier="TomO" created="200610121445" modified="200610231248" tags="systemConfig">
<pre>//{{{

config.macros.date.holidays=[ ]; // use reminders instead

if (config.options.chkGTDFancyStyle) {
 // these colours are somewhat friendlier to the default d3 menu colour scheme
 config.macros.calendar.weekendbg= &quot;seagreen&quot;;
 config.macros.calendar.monthbg = &quot;transparent&quot;;

 config.macros.date.weekendbg=&quot;seagreen&quot;;
 config.macros.date.holidaybg=&quot;seagreen&quot;;
 config.macros.date.modifiedsbg=&quot;transparent&quot;;
 config.macros.date.remindersbg=&quot;red&quot;;
}


//}}}</pre>
</div>
<div title="DefaultTiddlers" modifier="TomO" created="200809301742" modified="200810252055" changecount="5" creator="TomO">
<pre>[[About version 1.3.0]]
[[Summary Review]]</pre>
</div>
<div title="ExportTiddlersPlugin" modifier="ELSDesignStudios" created="200512240702" modified="200805272123" tags="systemConfig ImportExportPackage" changecount="2">
<pre>/***
|Name|ExportTiddlersPlugin|
|Source|http://www.TiddlyTools.com/#ExportTiddlersPlugin|
|Documentation|http://www.TiddlyTools.com/#ExportTiddlersPluginInfo|
|Version|2.7.0|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1|
|Type|plugin|
|Requires||
|Overrides||
|Description|select and extract tiddlers from your ~TiddlyWiki documents and save them to a separate file|
ExportTiddlersPlugin lets you select and extract tiddlers from your ~TiddlyWiki documents using interactive control panel lets you specify a destination, and then select which tiddlers to export. Tiddler data can be output as complete, stand-alone TiddlyWiki documents, or just the selected tiddlers (&quot;~PureStore&quot; format -- smaller files!) that can be imported directly into another ~TiddlyWiki, or as an ~RSS-compatible XML file that can be published for RSS syndication.
!!!!!Documentation
&gt;see [[ExportTiddlersPluginInfo]]
!!!!!Inline control panel (live):
&gt;&lt;&lt;exportTiddlers inline&gt;&gt;
!!!!!Revisions
&lt;&lt;&lt;
2008.05.27 [2.7.0] added ability to 'merge' with existing export file. Also, revised 'matchTags' functionality to be more robust and more efficient
|please see [[ExportTiddlersPluginInfo]] for additional revision details|
2005.10.09 [0.0.0] development started
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
// version
version.extensions.exportTiddlers = {major: 2, minor: 7, revision: 0, date: new Date(2008,5,27)};

// default shadow definition
config.shadowTiddlers.ExportTiddlers=&quot;&lt;&lt;exportTiddlers inline&gt;&gt;&quot;;

// add 'export' backstage task (following built-in import task)
if (config.tasks) { // TW2.2 or above
	config.tasks.exportTask = {
		text:&quot;export&quot;,
		tooltip:&quot;Export selected tiddlers to another file&quot;,
		content:&quot;&lt;&lt;exportTiddlers inline&gt;&gt;&quot;
	}
	config.backstageTasks.splice(config.backstageTasks.indexOf(&quot;importTask&quot;)+1,0,&quot;exportTask&quot;);
}

// macro handler
config.macros.exportTiddlers = {
	label: &quot;export tiddlers&quot;,
	prompt: &quot;Copy selected tiddlers to an export document&quot;,
	newdefault: &quot;export.html&quot;,
	datetimefmt: &quot;0MM/0DD/YYYY 0hh:0mm:0ss&quot; // for &quot;filter date/time&quot; edit fields
};

config.macros.exportTiddlers.handler = function(place,macroName,params) {
	if (params[0]!=&quot;inline&quot;)
		{ createTiddlyButton(place,this.label,this.prompt,onClickExportMenu); return; }
	var panel=createExportPanel(place);
	panel.style.position=&quot;static&quot;;
	panel.style.display=&quot;block&quot;;
}

function createExportPanel(place) {
	var panel=document.getElementById(&quot;exportPanel&quot;);
	if (panel) { panel.parentNode.removeChild(panel); }
	setStylesheet(config.macros.exportTiddlers.css,&quot;exportTiddlers&quot;);
	panel=createTiddlyElement(place,&quot;span&quot;,&quot;exportPanel&quot;,null,null)
	panel.innerHTML=config.macros.exportTiddlers.html;
	exportInitFilter();
	refreshExportList(0);
	var fn=document.getElementById(&quot;exportFilename&quot;);
	if (window.location.protocol==&quot;file:&quot; &amp;&amp; !fn.value.length) {
		// get new target path/filename
		var newPath=getLocalPath(window.location.href);
		var slashpos=newPath.lastIndexOf(&quot;/&quot;); if (slashpos==-1) slashpos=newPath.lastIndexOf(&quot;\\&quot;); 
		if (slashpos!=-1) newPath=newPath.substr(0,slashpos+1); // trim filename
		fn.value=newPath+config.macros.exportTiddlers.newdefault;
	}
	return panel;
}

function onClickExportMenu(e)
{
	if (!e) var e = window.event;
	var parent=resolveTarget(e).parentNode;
	var panel = document.getElementById(&quot;exportPanel&quot;);
	if (panel==undefined || panel.parentNode!=parent)
		panel=createExportPanel(parent);
	var isOpen = panel.style.display==&quot;block&quot;;
	if(config.options.chkAnimate)
		anim.startAnimating(new Slider(panel,!isOpen,e.shiftKey || e.altKey,&quot;none&quot;));
	else
		panel.style.display = isOpen ? &quot;none&quot; : &quot;block&quot; ;
	if (panel.style.display!=&quot;none&quot;) { // update list and set focus when panel is made visible
		refreshExportList(0);
		var fn=document.getElementById(&quot;exportFilename&quot;); fn.focus(); fn.select();
	}
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
	return(false);
}
//}}}

// // IE needs explicit scoping for functions called by browser events
//{{{
window.onClickExportMenu=onClickExportMenu;
window.onClickExportButton=onClickExportButton;
window.exportShowFilterFields=exportShowFilterFields;
window.refreshExportList=refreshExportList;
//}}}

// // CSS for floating export control panel
//{{{
config.macros.exportTiddlers.css = '\
#exportPanel {\
	display: none; position:absolute; z-index:12; width:35em; right:105%; top:6em;\
	background-color: #eee; color:#000; font-size: 8pt; line-height:110%;\
	border:1px solid black; border-bottom-width: 3px; border-right-width: 3px;\
	padding: 0.5em; margin:0em; -moz-border-radius:1em;\
}\
#exportPanel a, #exportPanel td a { color:#009; display:inline; margin:0px; padding:1px; }\
#exportPanel table { width:100%; border:0px; padding:0px; margin:0px; font-size:8pt; line-height:110%; background:transparent; }\
#exportPanel tr { border:0px;padding:0px;margin:0px; background:transparent; }\
#exportPanel td { color:#000; border:0px;padding:0px;margin:0px; background:transparent; }\
#exportPanel select { width:98%;margin:0px;font-size:8pt;line-height:110%;}\
#exportPanel input  { width:98%;padding:0px;margin:0px;font-size:8pt;line-height:110%; }\
#exportPanel textarea  { width:98%;padding:0px;margin:0px;overflow:auto;font-size:8pt; }\
#exportPanel .box { border:1px solid black; padding:3px; margin-bottom:5px; background:#f8f8f8; -moz-border-radius:5px; }\
#exportPanel .topline { border-top:2px solid black; padding-top:3px; margin-bottom:5px; }\
#exportPanel .rad { width:auto;border:0 }\
#exportPanel .chk { width:auto;border:0 }\
#exportPanel .btn { width:auto; }\
#exportPanel .btn1 { width:98%; }\
#exportPanel .btn2 { width:48%; }\
#exportPanel .btn3 { width:32%; }\
#exportPanel .btn4 { width:24%; }\
#exportPanel .btn5 { width:19%; }\
';
//}}}

// // HTML for export control panel interface
//{{{
config.macros.exportTiddlers.html = '\
&lt;!-- target path/file  --&gt;\
&lt;div&gt;\
export to path/filename:&lt;br&gt;\
&lt;input type=&quot;text&quot; id=&quot;exportFilename&quot; size=40 style=&quot;width:93%&quot;&gt;&lt;input \
	type=&quot;button&quot; id=&quot;exportBrowse&quot; value=&quot;...&quot; title=&quot;select or enter a local folder/file...&quot; style=&quot;width:5%&quot; \
	onclick=&quot;var fn=window.promptForExportFilename(this); if (fn.length) this.previousSibling.value=fn; &quot;&gt;\
&lt;/div&gt;\
\
&lt;!-- output format --&gt;\
&lt;div&gt;\
output file format:\
&lt;select id=&quot;exportFormat&quot; size=1&gt;\
&lt;option value=&quot;TW&quot;&gt;TiddlyWiki document (includes core code)&lt;/option&gt;\
&lt;option value=&quot;DIV&quot;&gt;TiddlyWiki &quot;PureStore&quot; file (tiddler data only)&lt;/option&gt;\
&lt;option value=&quot;XML&quot;&gt;XML (for RSS newsfeed)&lt;/option&gt;\
&lt;/select&gt;\
&lt;/div&gt;\
\
&lt;!-- notes --&gt;\
&lt;div&gt;\
notes:&lt;br&gt;\
&lt;textarea id=&quot;exportNotes&quot; rows=3 cols=40 style=&quot;height:4em;margin-bottom:5px;&quot; onfocus=&quot;this.select()&quot;&gt;&lt;/textarea&gt; \
&lt;/div&gt;\
\
&lt;!-- list of tiddlers --&gt;\
&lt;table&gt;&lt;tr align=&quot;left&quot;&gt;&lt;td&gt;\
	select:\
	&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportSelectAll&quot;\
		onclick=&quot;onClickExportButton(this)&quot; title=&quot;select all tiddlers&quot;&gt;\
		&amp;nbsp;all&amp;nbsp;&lt;/a&gt;\
	&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportSelectChanges&quot;\
		onclick=&quot;onClickExportButton(this)&quot; title=&quot;select tiddlers changed since last save&quot;&gt;\
		&amp;nbsp;changes&amp;nbsp;&lt;/a&gt; \
	&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportSelectOpened&quot;\
		onclick=&quot;onClickExportButton(this)&quot; title=&quot;select tiddlers currently being displayed&quot;&gt;\
		&amp;nbsp;opened&amp;nbsp;&lt;/a&gt; \
	&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportSelectRelated&quot;\
		onclick=&quot;onClickExportButton(this)&quot; title=&quot;select all tiddlers related (by link or transclusion) to the currently selected tiddlers&quot;&gt;\
		&amp;nbsp;related&amp;nbsp;&lt;/a&gt; \
	&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportToggleFilter&quot;\
		onclick=&quot;onClickExportButton(this)&quot; title=&quot;show/hide selection filter&quot;&gt;\
		&amp;nbsp;filter&amp;nbsp;&lt;/a&gt;  \
&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;\
	&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportListSmaller&quot;\
		onclick=&quot;onClickExportButton(this)&quot; title=&quot;reduce list size&quot;&gt;\
		&amp;nbsp;&amp;#150;&amp;nbsp;&lt;/a&gt;\
	&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportListLarger&quot;\
		onclick=&quot;onClickExportButton(this)&quot; title=&quot;increase list size&quot;&gt;\
		&amp;nbsp;+&amp;nbsp;&lt;/a&gt;\
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;\
&lt;select id=&quot;exportList&quot; multiple size=&quot;10&quot; style=&quot;margin-bottom:5px;&quot;\
	onchange=&quot;refreshExportList(this.selectedIndex)&quot;&gt;\
&lt;/select&gt;&lt;br&gt;\
&lt;/div&gt;&lt;!--box--&gt;\
\
&lt;!-- selection filter --&gt;\
&lt;div id=&quot;exportFilterPanel&quot; style=&quot;display:none&quot;&gt;\
&lt;table&gt;&lt;tr align=&quot;left&quot;&gt;&lt;td&gt;\
	selection filter\
&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;\
	&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportHideFilter&quot;\
		onclick=&quot;onClickExportButton(this)&quot; title=&quot;hide selection filter&quot;&gt;hide&lt;/a&gt;\
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;\
&lt;div class=&quot;box&quot;&gt;\
&lt;input type=&quot;checkbox&quot; class=&quot;chk&quot; id=&quot;exportFilterStart&quot; value=&quot;1&quot;\
	onclick=&quot;exportShowFilterFields(this)&quot;&gt; starting date/time&lt;br&gt;\
&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;&gt;&lt;tr valign=&quot;center&quot;&gt;&lt;td width=&quot;50%&quot;&gt;\
	&lt;select size=1 id=&quot;exportFilterStartBy&quot; onchange=&quot;exportShowFilterFields(this);&quot;&gt;\
		&lt;option value=&quot;0&quot;&gt;today&lt;/option&gt;\
		&lt;option value=&quot;1&quot;&gt;yesterday&lt;/option&gt;\
		&lt;option value=&quot;7&quot;&gt;a week ago&lt;/option&gt;\
		&lt;option value=&quot;30&quot;&gt;a month ago&lt;/option&gt;\
		&lt;option value=&quot;site&quot;&gt;SiteDate&lt;/option&gt;\
		&lt;option value=&quot;file&quot;&gt;file date&lt;/option&gt;\
		&lt;option value=&quot;other&quot;&gt;other (mm/dd/yyyy hh:mm)&lt;/option&gt;\
	&lt;/select&gt;\
&lt;/td&gt;&lt;td width=&quot;50%&quot;&gt;\
	&lt;input type=&quot;text&quot; id=&quot;exportStartDate&quot; onfocus=&quot;this.select()&quot;\
		onchange=&quot;document.getElementById(\'exportFilterStartBy\').value=\'other\';&quot;&gt;\
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;\
&lt;input type=&quot;checkbox&quot; class=&quot;chk&quot; id=&quot;exportFilterEnd&quot; value=&quot;1&quot;\
	onclick=&quot;exportShowFilterFields(this)&quot;&gt; ending date/time&lt;br&gt;\
&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;&gt;&lt;tr valign=&quot;center&quot;&gt;&lt;td width=&quot;50%&quot;&gt;\
	&lt;select size=1 id=&quot;exportFilterEndBy&quot; onchange=&quot;exportShowFilterFields(this);&quot;&gt;\
		&lt;option value=&quot;0&quot;&gt;today&lt;/option&gt;\
		&lt;option value=&quot;1&quot;&gt;yesterday&lt;/option&gt;\
		&lt;option value=&quot;7&quot;&gt;a week ago&lt;/option&gt;\
		&lt;option value=&quot;30&quot;&gt;a month ago&lt;/option&gt;\
		&lt;option value=&quot;site&quot;&gt;SiteDate&lt;/option&gt;\
		&lt;option value=&quot;file&quot;&gt;file date&lt;/option&gt;\
		&lt;option value=&quot;other&quot;&gt;other (mm/dd/yyyy hh:mm)&lt;/option&gt;\
	&lt;/select&gt;\
&lt;/td&gt;&lt;td width=&quot;50%&quot;&gt;\
	&lt;input type=&quot;text&quot; id=&quot;exportEndDate&quot; onfocus=&quot;this.select()&quot;\
		onchange=&quot;document.getElementById(\'exportFilterEndBy\').value=\'other\';&quot;&gt;\
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;\
&lt;input type=&quot;checkbox&quot; class=&quot;chk&quot; id=exportFilterTags value=&quot;1&quot;\
	onclick=&quot;exportShowFilterFields(this)&quot;&gt; match tags&lt;br&gt;\
&lt;input type=&quot;text&quot; id=&quot;exportTags&quot; onfocus=&quot;this.select()&quot;&gt;\
&lt;input type=&quot;checkbox&quot; class=&quot;chk&quot; id=exportFilterText value=&quot;1&quot;\
	onclick=&quot;exportShowFilterFields(this)&quot;&gt; match titles/tiddler text&lt;br&gt;\
&lt;input type=&quot;text&quot; id=&quot;exportText&quot; onfocus=&quot;this.select()&quot;&gt;\
&lt;/div&gt; &lt;!--box--&gt;\
&lt;/div&gt; &lt;!--panel--&gt;\
\
&lt;!-- action buttons --&gt;\
&lt;div style=&quot;text-align:center&quot;&gt;\
&lt;input type=button class=&quot;btn4&quot; onclick=&quot;onClickExportButton(this)&quot;\
	id=&quot;exportFilter&quot; value=&quot;apply filter&quot;&gt;\
&lt;input type=button class=&quot;btn4&quot; onclick=&quot;onClickExportButton(this)&quot;\
	id=&quot;exportStart&quot; value=&quot;export tiddlers&quot;&gt;\
&lt;input type=button class=&quot;btn4&quot; onclick=&quot;onClickExportButton(this)&quot;\
	id=&quot;exportDelete&quot; value=&quot;delete tiddlers&quot;&gt;\
&lt;input type=button class=&quot;btn4&quot; onclick=&quot;onClickExportButton(this)&quot;\
	id=&quot;exportClose&quot; value=&quot;close&quot;&gt;\
&lt;/div&gt;&lt;!--center--&gt;\
';
//}}}

// // initialize interface

// // exportInitFilter()
//{{{
function exportInitFilter() {
	// start date
	document.getElementById(&quot;exportFilterStart&quot;).checked=false;
	document.getElementById(&quot;exportStartDate&quot;).value=&quot;&quot;;
	// end date
	document.getElementById(&quot;exportFilterEnd&quot;).checked=false;
	document.getElementById(&quot;exportEndDate&quot;).value=&quot;&quot;;
	// tags
	document.getElementById(&quot;exportFilterTags&quot;).checked=false;
	document.getElementById(&quot;exportTags&quot;).value=&quot;&quot;;
	// text
	document.getElementById(&quot;exportFilterText&quot;).checked=false;
	document.getElementById(&quot;exportText&quot;).value=&quot;&quot;;
	// show/hide filter input fields
	exportShowFilterFields();
}
//}}}

// // exportShowFilterFields(which)
//{{{
function exportShowFilterFields(which) {
	var show;

	show=document.getElementById('exportFilterStart').checked;
	document.getElementById('exportFilterStartBy').style.display=show?&quot;block&quot;:&quot;none&quot;;
	document.getElementById('exportStartDate').style.display=show?&quot;block&quot;:&quot;none&quot;;
	var val=document.getElementById('exportFilterStartBy').value;
	document.getElementById('exportStartDate').value
		=getFilterDate(val,'exportStartDate').formatString(config.macros.exportTiddlers.datetimefmt);
	 if (which &amp;&amp; (which.id=='exportFilterStartBy') &amp;&amp; (val=='other'))
		document.getElementById('exportStartDate').focus();

	show=document.getElementById('exportFilterEnd').checked;
	document.getElementById('exportFilterEndBy').style.display=show?&quot;block&quot;:&quot;none&quot;;
	document.getElementById('exportEndDate').style.display=show?&quot;block&quot;:&quot;none&quot;;
	var val=document.getElementById('exportFilterEndBy').value;
	document.getElementById('exportEndDate').value
		=getFilterDate(val,'exportEndDate').formatString(config.macros.exportTiddlers.datetimefmt);
	 if (which &amp;&amp; (which.id=='exportFilterEndBy') &amp;&amp; (val=='other'))
		document.getElementById('exportEndDate').focus();

	show=document.getElementById('exportFilterTags').checked;
	document.getElementById('exportTags').style.display=show?&quot;block&quot;:&quot;none&quot;;

	show=document.getElementById('exportFilterText').checked;
	document.getElementById('exportText').style.display=show?&quot;block&quot;:&quot;none&quot;;
}
//}}}

// // onClickExportButton(which): control interactions
//{{{
function onClickExportButton(which)
{
	// DEBUG alert(which.id);
	var theList=document.getElementById('exportList'); if (!theList) return;
	var count = 0;
	var total = store.getTiddlers('title').length;
	switch (which.id)
		{
		case 'exportFilter':
			count=filterExportList();
			var panel=document.getElementById('exportFilterPanel');
			if (count==-1) { panel.style.display='block'; break; }
			document.getElementById(&quot;exportStart&quot;).disabled=(count==0);
			document.getElementById(&quot;exportDelete&quot;).disabled=(count==0);
			clearMessage(); displayMessage(&quot;filtered &quot;+formatExportMessage(count,total));
			if (count==0) { alert(&quot;No tiddlers were selected&quot;); panel.style.display='block'; }
			break;
		case 'exportStart':
			exportTiddlers();
			break;
		case 'exportDelete':
			exportDeleteTiddlers();
			break;
		case 'exportHideFilter':
		case 'exportToggleFilter':
			var panel=document.getElementById('exportFilterPanel')
			panel.style.display=(panel.style.display=='block')?'none':'block';
			break;
		case 'exportSelectChanges':
			var lastmod=new Date(document.lastModified);
			for (var t = 0; t &lt; theList.options.length; t++) {
				if (theList.options[t].value==&quot;&quot;) continue;
				var tiddler=store.getTiddler(theList.options[t].value); if (!tiddler) continue;
				theList.options[t].selected=(tiddler.modified&gt;lastmod);
				count += (tiddler.modified&gt;lastmod)?1:0;
			}
			document.getElementById(&quot;exportStart&quot;).disabled=(count==0);
			document.getElementById(&quot;exportDelete&quot;).disabled=(count==0);
			clearMessage(); displayMessage(formatExportMessage(count,total));
			if (count==0) alert(&quot;There are no unsaved changes&quot;);
			break;
		case 'exportSelectAll':
			for (var t = 0; t &lt; theList.options.length; t++) {
				if (theList.options[t].value==&quot;&quot;) continue;
				theList.options[t].selected=true;
				count += 1;
			}
			document.getElementById(&quot;exportStart&quot;).disabled=(count==0);
			document.getElementById(&quot;exportDelete&quot;).disabled=(count==0);
			clearMessage(); displayMessage(formatExportMessage(count,count));
			break;
		case 'exportSelectOpened':
			for (var t = 0; t &lt; theList.options.length; t++) theList.options[t].selected=false;
			var tiddlerDisplay = document.getElementById(&quot;tiddlerDisplay&quot;); // for TW2.1-
			if (!tiddlerDisplay) tiddlerDisplay = document.getElementById(&quot;storyDisplay&quot;); // for TW2.2+
			for (var t=0;t&lt;tiddlerDisplay.childNodes.length;t++) {
				var tiddler=tiddlerDisplay.childNodes[t].id.substr(7);
				for (var i = 0; i &lt; theList.options.length; i++) {
					if (theList.options[i].value!=tiddler) continue;
					theList.options[i].selected=true; count++; break;
				}
			}
			document.getElementById(&quot;exportStart&quot;).disabled=(count==0);
			document.getElementById(&quot;exportDelete&quot;).disabled=(count==0);
			clearMessage(); displayMessage(formatExportMessage(count,total));
			if (count==0) alert(&quot;There are no tiddlers currently opened&quot;);
			break;
		case 'exportSelectRelated':
			// recursively build list of related tiddlers
			function getRelatedTiddlers(tid,tids) {
				var t=store.getTiddler(tid); if (!t || tids.contains(tid)) return tids;
				tids.push(t.title);
				if (!t.linksUpdated) t.changed();
				for (var i=0; i&lt;t.links.length; i++)
					if (t.links[i]!=tid) tids=getRelatedTiddlers(t.links[i],tids);
				return tids;
			}
			// for all currently selected tiddlers, gather up the related tiddlers (including self) and select them as well
			var tids=[];
			for (var i=0; i&lt;theList.options.length; i++)
				if (theList.options[i].selected) tids=getRelatedTiddlers(theList.options[i].value,tids);
			// select related tiddlers (includes original selected tiddlers)
			for (var i=0; i&lt;theList.options.length; i++)
				theList.options[i].selected=tids.contains(theList.options[i].value);
			clearMessage(); displayMessage(formatExportMessage(tids.length,total));
			break;
		case 'exportListSmaller':	// decrease current listbox size
			var min=5;
			theList.size-=(theList.size&gt;min)?1:0;
			break;
		case 'exportListLarger':	// increase current listbox size
			var max=(theList.options.length&gt;25)?theList.options.length:25;
			theList.size+=(theList.size&lt;max)?1:0;
			break;
		case 'exportClose':
			document.getElementById('exportPanel').style.display='none';
			break;
		}
}
//}}}

// // promptForFilename(msg,path,file) uses platform/browser specific functions to get local filespec
//{{{
window.promptForExportFilename=function(here)
{
	var msg=here.title; // use tooltip as dialog box message
	var path=getLocalPath(document.location.href);
	var slashpos=path.lastIndexOf(&quot;/&quot;); if (slashpos==-1) slashpos=path.lastIndexOf(&quot;\\&quot;); 
	if (slashpos!=-1) path = path.substr(0,slashpos+1); // remove filename from path, leave the trailing slash
	var file=config.macros.exportTiddlers.newdefault;
	var result=&quot;&quot;;
	if(window.Components) { // moz
		try {
			netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
			var nsIFilePicker = window.Components.interfaces.nsIFilePicker;
			var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
			picker.init(window, msg, nsIFilePicker.modeSave);
			var thispath = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
			thispath.initWithPath(path);
			picker.displayDirectory=thispath;
			picker.defaultExtension='html';
			picker.defaultString=file;
			picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML);
			if (picker.show()!=nsIFilePicker.returnCancel) var result=picker.file.persistentDescriptor;
		}
		catch(e) { alert('error during local file access: '+e.toString()) }
	}
	else { // IE
		try { // XPSP2 IE only
			var s = new ActiveXObject('UserAccounts.CommonDialog');
			s.Filter='All files|*.*|Text files|*.txt|HTML files|*.htm;*.html|';
			s.FilterIndex=3; // default to HTML files;
			s.InitialDir=path;
			s.FileName=file;
			if (s.showOpen()) var result=s.FileName;
		}
		catch(e) {  // fallback
			var result=prompt(msg,path+file);
		}
	}
	return result;
}
//}}}

// // list display
//{{{
function formatExportMessage(count,total)
{
	var txt=total+' tiddler'+((total!=1)?'s':'')+&quot; - &quot;;
	txt += (count==0)?&quot;none&quot;:(count==total)?&quot;all&quot;:count;
	txt += &quot; selected for export&quot;;
	return txt;
}

function refreshExportList(selectedIndex)
{
	var theList  = document.getElementById(&quot;exportList&quot;);
	var sort;
	if (!theList) return;
	// get the sort order
	if (!selectedIndex)   selectedIndex=0;
	if (selectedIndex==0) sort='modified';
	if (selectedIndex==1) sort='title';
	if (selectedIndex==2) sort='modified';
	if (selectedIndex==3) sort='modifier';
	if (selectedIndex==4) sort='tags';

	// unselect headings and count number of tiddlers actually selected
	var count=0;
	for (var t=5; t &lt; theList.options.length; t++) {
		if (!theList.options[t].selected) continue;
		if (theList.options[t].value!=&quot;&quot;)
			count++;
		else { // if heading is selected, deselect it, and then select and count all in section
			theList.options[t].selected=false;
			for ( t++; t&lt;theList.options.length &amp;&amp; theList.options[t].value!=&quot;&quot;; t++) {
				theList.options[t].selected=true;
				count++;
			}
		}
	}

	// disable &quot;export&quot; and &quot;delete&quot; buttons if no tiddlers selected
	document.getElementById(&quot;exportStart&quot;).disabled=(count==0);
	document.getElementById(&quot;exportDelete&quot;).disabled=(count==0);
	// show selection count
	var tiddlers = store.getTiddlers('title');
	if (theList.options.length) { clearMessage(); displayMessage(formatExportMessage(count,tiddlers.length)); }

	// if a [command] item, reload list... otherwise, no further refresh needed
	if (selectedIndex&gt;4)  return;

	// clear current list contents
	while (theList.length &gt; 0) { theList.options[0] = null; }
	// add heading and control items to list
	var i=0;
	var indent=String.fromCharCode(160)+String.fromCharCode(160);
	theList.options[i++]=
		new Option(tiddlers.length+&quot; tiddlers in document&quot;, &quot;&quot;,false,false);
	theList.options[i++]=
		new Option(((sort==&quot;title&quot;        )?&quot;&gt;&quot;:indent)+' [by title]', &quot;&quot;,false,false);
	theList.options[i++]=
		new Option(((sort==&quot;modified&quot;)?&quot;&gt;&quot;:indent)+' [by date]', &quot;&quot;,false,false);
	theList.options[i++]=
		new Option(((sort==&quot;modifier&quot;)?&quot;&gt;&quot;:indent)+' [by author]', &quot;&quot;,false,false);
	theList.options[i++]=
		new Option(((sort==&quot;tags&quot;	)?&quot;&gt;&quot;:indent)+' [by tags]', &quot;&quot;,false,false);
	// output the tiddler list
	switch(sort)
		{
		case &quot;title&quot;:
			for(var t = 0; t &lt; tiddlers.length; t++)
				theList.options[i++] = new Option(tiddlers[t].title,tiddlers[t].title,false,false);
			break;
		case &quot;modifier&quot;:
		case &quot;modified&quot;:
			var tiddlers = store.getTiddlers(sort);
			// sort descending for newest date first
			tiddlers.sort(function (a,b) {if(a[sort] == b[sort]) return(0); else return (a[sort] &gt; b[sort]) ? -1 : +1; });
			var lastSection = &quot;&quot;;
			for(var t = 0; t &lt; tiddlers.length; t++)
				{
				var tiddler = tiddlers[t];
				var theSection = &quot;&quot;;
				if (sort==&quot;modified&quot;) theSection=tiddler.modified.toLocaleDateString();
				if (sort==&quot;modifier&quot;) theSection=tiddler.modifier;
				if (theSection != lastSection)
					{
					theList.options[i++] = new Option(theSection,&quot;&quot;,false,false);
					lastSection = theSection;
					}
				theList.options[i++] = new Option(indent+indent+tiddler.title,tiddler.title,false,false);
				}
			 break;
		case &quot;tags&quot;:
			var theTitles = {}; // all tiddler titles, hash indexed by tag value
			var theTags = new Array();
			for(var t=0; t&lt;tiddlers.length; t++) {
				var title=tiddlers[t].title;
				var tags=tiddlers[t].tags;
				if (!tags || !tags.length) {
					if (theTitles[&quot;untagged&quot;]==undefined) { theTags.push(&quot;untagged&quot;); theTitles[&quot;untagged&quot;]=new Array(); }
					theTitles[&quot;untagged&quot;].push(title);
				}
				else for(var s=0; s&lt;tags.length; s++) {
					if (theTitles[tags[s]]==undefined) { theTags.push(tags[s]); theTitles[tags[s]]=new Array(); }
					theTitles[tags[s]].push(title);
				}
			}
			theTags.sort();
			for(var tagindex=0; tagindex&lt;theTags.length; tagindex++) {
				var theTag=theTags[tagindex];
				theList.options[i++]=new Option(theTag,&quot;&quot;,false,false);
				for(var t=0; t&lt;theTitles[theTag].length; t++)
					theList.options[i++]=new Option(indent+indent+theTitles[theTag][t],theTitles[theTag][t],false,false);
			}
			break;
		}
	theList.selectedIndex=selectedIndex;		  // select current control item
	document.getElementById(&quot;exportStart&quot;).disabled=true;
	document.getElementById(&quot;exportDelete&quot;).disabled=true;
	clearMessage(); displayMessage(formatExportMessage(0,tiddlers.length));
}
//}}}

// // list filtering
//{{{
function getFilterDate(val,id)
{
	var result=0;
	switch (val) {
		case 'site':
			var timestamp=store.getTiddlerText(&quot;SiteDate&quot;);
			if (!timestamp) timestamp=document.lastModified;
			result=new Date(timestamp);
			break;
		case 'file':
			result=new Date(document.lastModified);
			break;
		case 'other':
			result=new Date(document.getElementById(id).value);
			break;
		default: // today=0, yesterday=1, one week=7, two weeks=14, a month=31
			var now=new Date(); var tz=now.getTimezoneOffset()*60000; now-=tz;
			var oneday=86400000;
			if (id=='exportStartDate')
				result=new Date((Math.floor(now/oneday)-val)*oneday+tz);
			else
				result=new Date((Math.floor(now/oneday)-val+1)*oneday+tz-1);
			break;
	}
	// DEBUG alert('getFilterDate('+val+','+id+')=='+result+&quot;\nnow=&quot;+now);
	return result;
}

function filterExportList()
{
	var theList  = document.getElementById(&quot;exportList&quot;); if (!theList) return -1;

	var filterStart=document.getElementById(&quot;exportFilterStart&quot;).checked;
	var val=document.getElementById(&quot;exportFilterStartBy&quot;).value;
	var startDate=getFilterDate(val,'exportStartDate');

	var filterEnd=document.getElementById(&quot;exportFilterEnd&quot;).checked;
	var val=document.getElementById(&quot;exportFilterEndBy&quot;).value;
	var endDate=getFilterDate(val,'exportEndDate');

	var filterTags=document.getElementById(&quot;exportFilterTags&quot;).checked;
	var tags=document.getElementById(&quot;exportTags&quot;).value;

	var filterText=document.getElementById(&quot;exportFilterText&quot;).checked;
	var text=document.getElementById(&quot;exportText&quot;).value;

	if (!(filterStart||filterEnd||filterTags||filterText)) {
		alert(&quot;Please set the selection filter&quot;);
		document.getElementById('exportFilterPanel').style.display=&quot;block&quot;;
		return -1;
	}
	if (filterStart&amp;&amp;filterEnd&amp;&amp;(startDate&gt;endDate)) {
		var msg=&quot;starting date/time:\n&quot;
		msg+=startDate.toLocaleString()+&quot;\n&quot;;
		msg+=&quot;is later than ending date/time:\n&quot;
		msg+=endDate.toLocaleString()
		alert(msg);
		return -1;
	}

	// if filter by tags, set up conditional expression
	if (filterTags) {
		var all = store.getTags(); // get list of all tags
		for (var i=0; i&lt;all.length; i++) all[i]=all[i][0]; // remove tag counts
		// convert &quot;tag1 AND ( tag2 OR NOT tag3 )&quot;
		// into javascript expression containing regexp tests:
		// &quot;/\~tag1\~/.test(...) &amp;&amp; ( /\~tag2\~/.test(...) || ! /\~tag2\~/.test(...) )&quot;
		var c=tags;
		c = c.replace(/[\[\]]/g,&quot;&quot;); // remove [[...]] quoting around tagvalues
		// change AND/OR/NOT/parens to javascript operators and delimit terms with &quot;~&quot;
		c = c.replace(/\sand\s/ig,&quot;~&amp;&amp;~&quot;);
		c = c.replace(/\sor\s/ig,&quot;~||~&quot;);
		c = c.replace(/(\s)?not([\s\(])/ig,&quot;~!~$2&quot;);
		c = c.replace(/([\(\)])/ig,&quot;~$1~&quot;);
		// change existing tags to regexp tests and non-existing tags to &quot;false&quot;
		var terms=c.split(&quot;~&quot;);
		for (var i=0; i&lt;terms.length; i++) { var t=terms[i];
			if (/(&amp;&amp;)|(\|\|)|[!\(\)]/.test(t) || t==&quot;&quot;) continue; // skip operators/parens/spaces
			terms[i]=!all.contains(t)?&quot;false&quot;:(&quot;/\\~&quot;+t+&quot;\\~/.test(tiddlertags)&quot;);
		}
		c=terms.join(&quot; &quot;);
	}
	function matchTags(t,c) {
		if (!c||!c.trim().length) return false;
		// assemble tags from tiddler into string &quot;~tag1~tag2~tag3~&quot;
		var tiddlertags = &quot;~&quot;+t.tags.join(&quot;~&quot;)+&quot;~&quot;;
		// eval string against boolean test expression
		try { if(eval(c)) return true; }
		catch(e) { displayMessage(e.toString()); }
		return false;
	}
	
	// scan list and select tiddlers that match all applicable criteria
	var total=0;
	var count=0;
	for (var i=0; i&lt;theList.options.length; i++) {
		// get item, skip non-tiddler list items (section headings)
		var opt=theList.options[i]; if (opt.value==&quot;&quot;) continue;
		// get tiddler, skip missing tiddlers (this should NOT happen)
		var tiddler=store.getTiddler(opt.value); if (!tiddler) continue; 
		var sel=true;
		if ( (filterStart &amp;&amp; tiddler.modified&lt;startDate)
		|| (filterEnd &amp;&amp; tiddler.modified&gt;endDate)
		|| (filterTags &amp;&amp; !matchTags(tiddler,c))
		|| (filterText &amp;&amp; (tiddler.text.indexOf(text)==-1) &amp;&amp; (tiddler.title.indexOf(text)==-1)))
			sel=false;
		opt.selected=sel;
		count+=sel?1:0;
		total++;
	}
	return count;
}
//}}}

// // OUTPUT FORMATTING AND FILE I/O
//{{{
function exportTWHeader()
{
	// get the TiddlyWiki core code source
	var sourcefile=getLocalPath(document.location.href);
	var source=loadFile(sourcefile);
	if(source==null) { alert(config.messages.cantSaveError); return null; }
	// reset existing HTML source markup
	source=updateMarkupBlock(source,&quot;PRE-HEAD&quot;);
	source=updateMarkupBlock(source,&quot;POST-HEAD&quot;);
	source=updateMarkupBlock(source,&quot;PRE-BODY&quot;);
	source=updateMarkupBlock(source,&quot;POST-BODY&quot;);
	// find store area
	var posOpeningDiv=source.indexOf(startSaveArea);
	var posClosingDiv=source.lastIndexOf(endSaveArea);
	if((posOpeningDiv==-1)||(posClosingDiv==-1))
		{ alert(config.messages.invalidFileError.format([sourcefile])); return; }
	// return everything up to store area
	return source.substr(0,posOpeningDiv+startSaveArea.length);
}

function exportTWFooter()
{
	// get the TiddlyWiki core code source
	var sourcefile=getLocalPath(document.location.href);
	var source=loadFile(sourcefile);
	if(source==null) { alert(config.messages.cantSaveError); return null; }
	// reset existing HTML source markup
	source=updateMarkupBlock(source,&quot;PRE-HEAD&quot;);
	source=updateMarkupBlock(source,&quot;POST-HEAD&quot;);
	source=updateMarkupBlock(source,&quot;PRE-BODY&quot;);
	source=updateMarkupBlock(source,&quot;POST-BODY&quot;);
	// find store area
	var posOpeningDiv=source.indexOf(startSaveArea);
	var posClosingDiv=source.lastIndexOf(endSaveArea);
	if((posOpeningDiv==-1)||(posClosingDiv==-1))
		{ alert(config.messages.invalidFileError.format([sourcefile])); return; }
	// return everything after store area
	return source.substr(posClosingDiv);
}

function exportDIVHeader()
{
	var out=[];
	var now = new Date();
	var title = convertUnicodeToUTF8(wikifyPlain(&quot;SiteTitle&quot;).htmlEncode());
	var subtitle = convertUnicodeToUTF8(wikifyPlain(&quot;SiteSubtitle&quot;).htmlEncode());
	var user = convertUnicodeToUTF8(config.options.txtUserName.htmlEncode());
	var twver = version.major+&quot;.&quot;+version.minor+&quot;.&quot;+version.revision;
	var pver = version.extensions.exportTiddlers.major+&quot;.&quot;
		+version.extensions.exportTiddlers.minor+&quot;.&quot;+version.extensions.exportTiddlers.revision;
	out.push(&quot;&lt;html&gt;&lt;body&gt;&quot;);
	out.push(&quot;&lt;style type=\&quot;text/css\&quot;&gt;&quot;);
	out.push(&quot;#storeArea {display:block;margin:1em;}&quot;);
	out.push(&quot;#storeArea div&quot;);
	out.push(&quot;{padding:0.5em;margin:1em;border:2px solid black;height:10em;overflow:auto;}&quot;);
	out.push(&quot;#javascriptWarning&quot;);
	out.push(&quot;{width:100%;text-align:left;background-color:#eeeeee;padding:1em;}&quot;);
	out.push(&quot;&lt;/style&gt;&quot;);
	out.push(&quot;&lt;div id=\&quot;javascriptWarning\&quot;&gt;&quot;);
	out.push(&quot;TiddlyWiki export file&lt;br&gt;&quot;);
	out.push(&quot;Source&quot;+&quot;: &lt;b&gt;&quot;+convertUnicodeToUTF8(document.location.href)+&quot;&lt;/b&gt;&lt;br&gt;&quot;);
	out.push(&quot;Title: &lt;b&gt;&quot;+title+&quot;&lt;/b&gt;&lt;br&gt;&quot;);
	out.push(&quot;Subtitle: &lt;b&gt;&quot;+subtitle+&quot;&lt;/b&gt;&lt;br&gt;&quot;);
	out.push(&quot;Created: &lt;b&gt;&quot;+now.toLocaleString()+&quot;&lt;/b&gt; by &lt;b&gt;&quot;+user+&quot;&lt;/b&gt;&lt;br&gt;&quot;);
	out.push(&quot;TiddlyWiki &quot;+twver+&quot; / &quot;+&quot;ExportTiddlersPlugin &quot;+pver+&quot;&lt;br&gt;&quot;);
	out.push(&quot;Notes:&lt;hr&gt;&lt;pre&gt;&quot;+document.getElementById(&quot;exportNotes&quot;).value.replace(/\n/g,&quot;&lt;br&gt;&quot;)+&quot;&lt;/pre&gt;&quot;);
	out.push(&quot;&lt;/div&gt;&quot;);
	out.push(&quot;&lt;div id=\&quot;storeArea\&quot;&gt;&quot;);
	return out;
}

function exportDIVFooter()
{
	return [&quot;&lt;/div&gt;&lt;!--POST-BODY-START--&gt;\n&lt;!--POST-BODY-END--&gt;&lt;/body&gt;&lt;/html&gt;&quot;];
}

function exportXMLHeader()
{
	var out=[];
	var now = new Date();
	var u = store.getTiddlerText(&quot;SiteUrl&quot;,null);
	var title = convertUnicodeToUTF8(wikifyPlain(&quot;SiteTitle&quot;).htmlEncode());
	var subtitle = convertUnicodeToUTF8(wikifyPlain(&quot;SiteSubtitle&quot;).htmlEncode());
	var user = convertUnicodeToUTF8(config.options.txtUserName.htmlEncode());
	var twver = version.major+&quot;.&quot;+version.minor+&quot;.&quot;+version.revision;
	var pver = version.extensions.exportTiddlers.major+&quot;.&quot;
		+version.extensions.exportTiddlers.minor+&quot;.&quot;+version.extensions.exportTiddlers.revision;
	out.push(&quot;&lt;&quot; + &quot;?xml version=\&quot;1.0\&quot;?&quot; + &quot;&gt;&quot;);
	out.push(&quot;&lt;rss version=\&quot;2.0\&quot;&gt;&quot;);
	out.push(&quot;&lt;channel&gt;&quot;);
	out.push(&quot;&lt;title&gt;&quot; + title + &quot;&lt;/title&gt;&quot;);
	if(u) out.push(&quot;&lt;link&gt;&quot; + convertUnicodeToUTF8(u.htmlEncode()) + &quot;&lt;/link&gt;&quot;);
	out.push(&quot;&lt;description&gt;&quot; + subtitle + &quot;&lt;/description&gt;&quot;);
	out.push(&quot;&lt;language&gt;en-us&lt;/language&gt;&quot;);
	out.push(&quot;&lt;copyright&gt;Copyright &quot; + now.getFullYear() + &quot; &quot; + user + &quot;&lt;/copyright&gt;&quot;);
	out.push(&quot;&lt;pubDate&gt;&quot; + now.toGMTString() + &quot;&lt;/pubDate&gt;&quot;);
	out.push(&quot;&lt;lastBuildDate&gt;&quot; + now.toGMTString() + &quot;&lt;/lastBuildDate&gt;&quot;);
	out.push(&quot;&lt;docs&gt;http://blogs.law.harvard.edu/tech/rss&lt;/docs&gt;&quot;);
	out.push(&quot;&lt;generator&gt;TiddlyWiki &quot;+twver+&quot; plus ExportTiddlersPlugin &quot;+pver+&quot;&lt;/generator&gt;&quot;);
	return out;
}

function exportXMLFooter()
{
	return [&quot;&lt;/channel&gt;&lt;/rss&gt;&quot;];
}

function exportData(target,list,fmt)
{
	function getData(s,f,t) { var r=&quot;&quot;;
		switch (f) {
			case &quot;TW&quot;: r=s.getSaver().externalizeTiddler(s,t); break;
			case &quot;DIV&quot;: r=t.title+&quot;\n&quot;+s.getSaver().externalizeTiddler(s,t); break;
			case &quot;XML&quot;: r=t.saveToRss(store.getTiddlerText(&quot;SiteUrl&quot;,&quot;&quot;)); break;
		}
		return convertUnicodeToUTF8(r);
	}

	var out=[]; var tids=[];
	// get selected tiddlers
	for (var i=0; i&lt;list.options.length; i++) {
		var opt=list.options[i]; if (!opt.selected||!opt.value.length) continue;
		var tid=store.getTiddler(opt.value); if (!tid) continue;
		tids.push(tid.title);
		out.push(getData(store,fmt,tid));
	}
	var count=out.length;
	// merge with existing tiddlers
	var text=loadFile(target);
	if (text &amp;&amp; text.length) {
		var msg=target+&quot;\nalready contains tiddler definitions.\n&quot;;
		msg+=&quot;\nPress OK to add new/revised tiddlers to current file contents.&quot;;
		msg+=&quot;\nPress Cancel to completely replace file contents&quot;;
		var remoteStore=new TiddlyWiki();
		if (remoteStore.importTiddlyWiki(text) &amp;&amp; confirm(msg)) {
			var existing=remoteStore.getTiddlers(&quot;title&quot;);
			for (var i=0; i&lt;existing.length; i++)
				if (!tids.contains(existing[i].title))
					out.push(getData(remoteStore,fmt,existing[i]));
			var msg=&quot;Merged %0 new/revised tiddlers and %1 existing tiddlers&quot;;
			displayMessage(msg.format([count,out.length-count]));
		}
	}
	return out;
}
//}}}

// // exportTiddlers(): output selected data to local file
//{{{
function exportTiddlers()
{
	clearMessage();
	var list  = document.getElementById(&quot;exportList&quot;); if (!list) return;
	var fmt = document.getElementById(&quot;exportFormat&quot;).value;
	var target = document.getElementById(&quot;exportFilename&quot;).value.trim();
	if (!target.length) {
		displayMessage(&quot;A local target path/filename is required&quot;,target);
		return;
	}
	switch (fmt) {
		case &quot;TW&quot;:	var head=exportTWHeader(); break;
		case &quot;DIV&quot;:	var head=exportDIVHeader(); break;
		case &quot;XML&quot;:	var head=exportXMLHeader(); break;
	}
	var theData=exportData(target,list,fmt);
	var c=theData.length;
	switch (fmt) {
		case &quot;TW&quot;:	var foot=exportTWFooter(); break;
		case &quot;DIV&quot;:	var foot=exportDIVFooter(); break;
		case &quot;XML&quot;:	var foot=exportXMLFooter(); break;
	}
	var out=[]; var txt=out.concat(head,theData,foot).join(&quot;\n&quot;);
	var msg=&quot;An error occurred while saving to &quot;+target;
	if (saveFile(target,txt)) msg=c+&quot; tiddler&quot;+((c!=1)?&quot;s&quot;:&quot;&quot;)+&quot; written to &quot;+target;
	displayMessage(msg,&quot;file:///&quot;+target);
}
//}}}

// // exportDeleteTiddlers(): delete selected tiddlers from file
//{{{
function exportDeleteTiddlers()
{
	var list=document.getElementById(&quot;exportList&quot;); if (!list) return;
	var tids=[];
	for (i=0;i&lt;list.length;i++)
		if (list.options[i].selected &amp;&amp; list.options[i].value.length)
			tids.push(list.options[i].value);
	if (!confirm(&quot;Are you sure you want to delete these tiddlers:\n\n&quot;+tids.join(', '))) return;
	store.suspendNotifications();
	for (t=0;t&lt;tids.length;t++) {
		var tid=store.getTiddler(tids[t]); if (!tid) continue;
		if (tid.tags.contains(&quot;systemConfig&quot;))
			if (!confirm(&quot;'&quot;+tid.title+&quot;' is tagged with 'systemConfig'.\n\nRemoving this tiddler may cause unexpected results.  Are you sure?&quot;))
				continue;
		store.removeTiddler(tid.title);
		story.closeTiddler(tid.title);
	}
	store.resumeNotifications();
	alert(tids.length+&quot; tiddlers deleted&quot;);
	refreshExportList(0); // reload listbox
	store.notifyAll(); // update page display
}
//}}}</pre>
</div>
<div title="GTDMenu" modifier="TomO" created="200603151633" modified="200810251748" tags="gtd" changecount="1">
<pre>+++(gtdProjectsSliderState)[Projects]&lt;&lt;tiddler ProjectList&gt;&gt;===
+++(gtdActionsSliderState)[Actions]&lt;&lt;list tagged context&gt;&gt;===
+++(gtdReviewSliderState)[Review]
*[[Summary Review]]
*[[Project Review]]
*[[Action Review]]
*[[Reminders]]
===

&lt;&lt;newTiddler label:&quot;Create new project&quot; prompt:&quot;Create a new project&quot; title:&quot;NewProject&quot; tag:&quot;project&quot; text:{{store.getTiddlerText('NewProjectTemplate')}} focus:title&gt;&gt; &lt;&lt;newTiddler label:&quot;Create new context&quot; prompt:&quot;Create a new context&quot; title:&quot;NewContext&quot; tag:&quot;context&quot; text:{{store.getTiddlerText('NewContextTemplate')}} focus:title&quot;&gt;&gt; &lt;&lt;newTiddler label:&quot;Create new action&quot; prompt:&quot;Create a new action&quot; title:&quot;NewAction&quot; tag:&quot;action&quot; text:{{store.getTiddlerText('NewActionTemplate')}} focus:title&gt;&gt;
[[Ticklers]] +++(gtdReferenceSliderState)[Reference]&lt;&lt;list tagged reference&gt;&gt;=== +++(gtdSomedaySliderState)[Someday-Maybe]&lt;&lt;list tagged someday&gt;&gt;===
+++[Calendar|Show a calendar]&lt;&lt;calendar thismonth&gt;&gt;===

[[Configuration|Configuration Options]] [[Check for Updates|UpdateApplication]] [[Archives]]</pre>
</div>
<div title="GTDPlugins" modifier="TomO" created="200603020500" modified="200810271732" tags="gtd systemConfig" changecount="9">
<pre>/***
|''Name:''|GTDPlugins|
|''Description:''|Plugin to support Getting Things Done|
|''Version:''|&lt;&lt;gtdVersion&gt;&gt;|
|''Date:''|October 27, 2008|
|''Source:''|http://www.dcubed.ca/|
|''Author:''|Tom Otvos|
|''CoreVersion:''|2.4|
|''Browser:''|Firefox 1.5+; InternetExplorer 6.0+; Safari 3.1+|
***/

/***
''Macros:''
*{{{&lt;&lt;gtdAction &quot;}}}//title//{{{&quot; &quot;}}}//context list//{{{&quot;&gt;&gt;}}}
*{{{&lt;&lt;gtdActionList {&quot;}}}//context list//{{{&quot; | &quot;*&quot; | &quot;@&quot; {&quot;all&quot; | &quot;noproject&quot; | &quot;projectonly&quot;} }&gt;&gt;}}}
** //if no parameters are specified, current context or project is used//
** //specify &quot;*&quot; for actions across all projects, &quot;@&quot; for incomplete actions across all contexts (or &quot;all&quot; for all actions)//
** //use &quot;projectonly&quot; or &quot;noproject&quot; to filter actions by project association//
*{{{&lt;&lt;list tagged &quot;}}}//tag list//{{{&quot; {any | all}&gt;&gt;}}}
** //if no parameters are specified, all tags are necessary//
*{{{&lt;&lt;gtdActionCompleted&gt;&gt;}}}
*{{{&lt;&lt;gtdToggleTag&gt;&gt;}}}
*{{{&lt;&lt;gtdToggleState&gt;&gt;}}}
*{{{&lt;&lt;importUpdates &quot;}}}//url//{{{&quot; {updates | all} &quot;}}}//buttonTitle//{{{&quot; &quot;}}}//buttonHelp//{{{&quot; &quot;}}}//loadTiddlers params...//{{{&quot;&gt;&gt;}}}
*{{{&lt;&lt;gtdArchive { archive | unarchive | purge }&gt;&gt;}}}

''Commands:''
*{{{newAction}}}
*{{{newProjectAction}}}
*{{{changeContext}}}
*{{{deleteAction}}}
*{{{deleteContext}}}
*{{{deleteProject}}}
*{{{deleteProjectAll}}}
*{{{projectify}}}

''Wiki formatting:''
*{{{..new action title|context}}}

***/
//{{{

version.extensions.GTDPlugins = {major: 1, minor: 3, revision: 0, patch: 0 };

var _GTD = {

	lazyAutoSave: 0,
	contextCache: null,
	usingProjectTags: true,
	projectPriorities: [],

	initialize: function ()
	{
		var d = new Date();

		if (config.options.txtGTDReferenceContext == undefined) config.options.txtGTDReferenceContext = &quot;reference&quot;;
		if (config.options.txtGTDSomedayContext == undefined) config.options.txtGTDSomedayContext = &quot;someday&quot;;
		if (config.options.txtGTDUnfiledContext == undefined) config.options.txtGTDUnfiledContext = &quot;unfiled&quot;;
		if (config.options.txtGTDActionAging == undefined) config.options.txtGTDActionAging = &quot;&quot;;
		if (config.options.chkGTDFancyStyle == undefined) config.options.chkGTDFancyStyle = true;
		if (config.options.chkGTDLazyAutoSave == undefined) config.options.chkGTDLazyAutoSave = true;
		if (config.options.txtGTDLazyAutoSaveInterval == undefined) config.options.txtGTDLazyAutoSaveInterval = &quot;60&quot;;
		if (config.options.txtGTDProjectPriorities == undefined) config.options.txtGTDProjectPriorities = &quot;&quot;;
		
		// some tricks to work when our script is loaded from an external file...
		if (!store) config.notifyTiddlers.push( {name: &quot;GTDStyleSheet&quot;, notify: refreshStyles} );
		if (!store &amp;&amp; config.options.chkGTDFancyStyle) config.notifyTiddlers.push( {name: &quot;GTDTWStyleSheet&quot;, notify: refreshStyles} );
		if (!store) config.notifyTiddlers.push( {name: null, notify: _GTD.refreshActionViews} );
		
		this.setReviewUpdate();
		this.setLazyAutoSave();
		
		if (config.options.txtGTDProjectPriorities.length == 0)
			this.projectPriorities = [ &quot;important&quot; ];
		else
			this.projectPriorities = config.options.txtGTDProjectPriorities.split(';');
		
		if (!store) return;
		
		if ((version.major == 2 &amp;&amp; version.minor &lt; 1) || !store.tiddlerExists(&quot;d3 settings&quot;)) {
			// we force a changed() call on all projects, contexts, and actions, to enable them to set up their cross-references
			var tiddlers = [];
			tiddlers = tiddlers.concat(store.getTaggedTiddlers(&quot;project&quot;), store.getTaggedTiddlers(&quot;context&quot;), store.getTaggedTiddlers(&quot;action&quot;));
			for (var i = 0; i &lt; tiddlers.length; i++)
				tiddlers[i].changed();
			
			// if we have tiddler meta data, rebuild it
			if (version.major &gt; 2 || version.minor &gt; 0)
				this.rebuildMetaData();
		}
		else {
			this.initializeFromMetaData();
			this.usingProjectTags = false;
		}
		
		//store.addNotification(&quot;GTDStyleSheet&quot;, refreshStyles);
		//if (config.options.chkGTDFancyStyle) store.addNotification(&quot;GTDTWStyleSheet&quot;, refreshStyles);
		store.addNotification(null, _GTD.refreshActionViews);
		
		// force a display of release notes, if required
		var v = version.extensions.GTDPlugins;
		var releaseNotesTiddler = &quot;About version &quot; + v.major + '.' + v.minor + '.' + v.revision;
		if ((config.options.chkGTDReleaseNotes || config.options.chkGTDReleaseNotes == undefined) &amp;&amp; store.tiddlerExists(releaseNotesTiddler)) {
			params = &quot;open:\&quot;&quot; + releaseNotesTiddler + &quot;\&quot;&quot;;
			params = params.parseParams(&quot;open&quot;,null,false);
			config.options.chkGTDReleaseNotes = false;
			saveOptionCookie(&quot;chkGTDReleaseNotes&quot;);
		}

		if (version.major &gt; 2 || version.minor &gt; 0)
			pluginInfo.log.push('Initialized in ' + ((new Date()).getTime() - d.getTime()) + ' milliseconds');
	},
	
	rebuildMetaData: function()
	{
		pluginInfo.log.push('Rebuilding action metadata...');

		store.suspendNotifications();
		var tiddlers = store.getTaggedTiddlers(&quot;action&quot;);
		for (var i = 0; i &lt; tiddlers.length; i++) {
			var t = tiddlers[i];
			store.setValue(t, &quot;gtd&quot;);
			store.setValue(t, &quot;gtd.context&quot;, t.gtdContextName);
			if (t.gtdProject) store.setValue(t, &quot;gtd.project&quot;, t.gtdProject.title);
			if (t.gtdProject) store.setValue(t, &quot;gtd.projectindex&quot;, t.gtdProject.gtdActions.indexOf(t));
			// booo...scary...strip out project tag
			if (t.gtdProject) t.tags.remove(t.gtdProject.title);
		}
		store.resumeNotifications();
			
		var tiddler = store.createTiddler(&quot;d3 conversion&quot;);
		var s = &quot;Completed document conversion. Do not delete this tiddler unless you want to rebuild the action metadata.\n\nThis tiddler also contains document-specific preferences which, if deleted, will revert to default settings.&quot;;
		tiddler.assign(&quot;d3 settings&quot;, s, config.options.txtUserName, new Date(), [&quot;excludeLists&quot;]);
	},
	
	initializeFromMetaData: function()
	{
		var tiddlers = store.getTaggedTiddlers(&quot;action&quot;);
		// ??? one possible optimization is to sort action list by project, to avoid repeatedly fetching project tiddler
		for (var i = 0; i &lt; tiddlers.length; i++) {
			var t = tiddlers[i];
			t.gtdActionName = store.getValue(t, &quot;title&quot;);
			t.gtdActionDone = this.tiddlerHasTag(t, &quot;done&quot;);
			t.gtdProjectName = store.getValue(t, &quot;gtd.project&quot;);
			t.gtdContextName = store.getValue(t, &quot;gtd.context&quot;);
			if (t.gtdProjectName) {
				t.gtdProject = store.getTiddler(t.gtdProjectName);
				if (t.gtdProject) {
					if (t.gtdProject.gtdActions == undefined) t.gtdProject.gtdActions = [];
					t.gtdProject.gtdActions.push(t);
				}
			}
		}
		
		tiddlers = store.getTaggedTiddlers(&quot;project&quot;);
		for (i = 0; i &lt; tiddlers.length; i++) {
			t = tiddlers[i];
			if (t.gtdActions) {
				t.gtdActions.sort( 
					function(a,b) { var ai = parseInt(store.getValue(a, &quot;gtd.projectindex&quot;)), bi = parseInt(store.getValue(b, &quot;gtd.projectindex&quot;)); return (ai &lt; bi) ? -1 : +1; }
				);
			}
			else
				t.gtdActions = [];
			this.setNextAction(t);
		}
		
		tiddlers = this.getCachedContexts();
		for (i = 0; i &lt; tiddlers.length; i++)
			tiddlers[i].gtdContextName = tiddlers[i].title;
	},
	
	tiddlerHasTag: function (tiddler, tag)
	{
		if (typeof(tiddler) == &quot;string&quot;) tiddler = store.getTiddler(tiddler);
		return tiddler.isTagged(tag);
	},
	
	tiddlerSwapTag: function (tiddler, oldTag, newTag)
	{
		for (var i = 0; i &lt; tiddler.tags.length; i++)
			if (tiddler.tags[i] == oldTag) {
				tiddler.tags[i] = newTag;
				return true;
			}
		return false;
	},
	
	setExtendedValue: function (tiddler, name, value)
	{
		// this bottleneck safely sets an extended data value, quietly ignoring the request
		// if the setValue function is not available AND it disables notifications during
		// the setValue if it is defined
		
		if (version.major &gt; 2 || version.minor &gt; 0) {
			store.suspendNotifications();
			store.setValue(tiddler, name, value);
			store.resumeNotifications();
		}
	},
	
	tiddlerHasChanged: function (tiddler, doSave)
	{
		tiddler.changed();
		//story.setDirty(tiddler.title, true);
		store.setDirty(true);
		if (this.tiddlerHasTag(tiddler, 'context'))
			this.clearContextCache();
		if (doSave == undefined) doSave = true;
		if (doSave) tiddler.modified = new Date();
		if (config.options.chkAutoSave &amp;&amp; doSave)
			saveChanges();
		else if (doSave)
			this.lazyAutoSave++;
	},
	
	tiddlerAgeInDays: function(tiddler)
	{
		var now = new Date();
		return (now.getTime() - tiddler.modified.getTime()) / 1000 / 86400;
	},
	
	filteredTags: function (tags, specialTags, filterTags)
	{
		var resultTags = [];
		specialTags = specialTags.concat(filterTags);
		for (var i = 0; i &lt; tags.length; i++)
			if (!specialTags.contains(tags[i])) resultTags.push(tags[i]);
		return resultTags;
	},
	
	filteredActionTags: function (tags, filterTags)
	{
		return this.filteredTags(tags, [ &quot;action&quot;, &quot;done&quot;, &quot;floating&quot;, &quot;action-archive&quot; ], filterTags);
	},
	
	filteredProjectTags: function (tags, filterTags)
	{
		return this.filteredTags(tags, [ &quot;project&quot;, &quot;done&quot;, &quot;important&quot;, &quot;project-archive&quot; ], filterTags);
	},
	
	qualifiedProjectName: function(p)
	{
		var tags = this.filteredProjectTags(p.tags, []);
		var q = '';
		for (var i = 0; i &lt; tags.length; i++)
			q += tags[i] + '.';
		return q + p.title;
	},
	
	toggleTag: function (tiddler, tag, toggle)
	{
		var tagIndex = -1;
		for (var i = 0; i &lt; tiddler.tags.length; i++)
			if (tiddler.tags[i] == tag) {
				tagIndex = i;
				break;
			}
		
		if (toggle &amp;&amp; tagIndex == -1) {
			tiddler.tags.push(tag);
		}
		else if (!toggle &amp;&amp; tagIndex != -1) {
			tiddler.tags.splice(tagIndex, 1);
		}
	},
	
	getTiddlerElement: function (tiddler)
	{
		return document.getElementById(story.idPrefix + tiddler.title);
	},
	
	refreshActionViews: function (tiddler)
	{
		if (tiddler) {
			if (typeof(tiddler) == &quot;string&quot;) tiddler = store.getTiddler(tiddler);
			if (tiddler) {
				// first refresh the action tiddler
				story.refreshTiddler(tiddler.title, null, true);
				// do not do anything else if we are not an action!
				// no, we still want to do review updates below, so only do the next bit for actions
				// if (!_GTD.tiddlerHasTag(tiddler, &quot;action&quot;)) return;
				if (_GTD.tiddlerHasTag(tiddler, &quot;action&quot;)) {
					// now refresh all tiddlers that are tags of the action, which should be the context and project
					// no...now use explicit reference to project/context
					//for (var i = 0; i &lt; tiddler.tags.length; i++)
						// ...of course, we don't refresh action-specific state tags
					//	if (tiddler.tags[i] != &quot;action&quot; &amp;&amp; tiddler.tags[i] != &quot;done&quot; &amp;&amp; tiddler.tags[i] != &quot;floating&quot;) {
					//		story.refreshTiddler(tiddler.tags[i], null, true);
					//	}
					if (tiddler.gtdProjectName &amp;&amp; tiddler.gtdProjectName.length &gt; 0) story.refreshTiddler(tiddler.gtdProjectName, null, true);
					// because an action change can affect multiple contexts (via next actions), the only way we can ensure that everything
					// gets updated is to refresh all the displayed contexts...hopefully, there won't be many open and DOM lookups fairly snappy
					// if (tiddler.gtdContextName &amp;&amp; tiddler.gtdContextName.length &gt; 0) story.refreshTiddler(tiddler.gtdContextName, null, true);
					_GTD.refreshAllContexts();
				}
				else if (_GTD.tiddlerHasTag(tiddler, &quot;context&quot;))
					_GTD.clearContextCache();
			}
		}
	
		var specialTiddlers = store.getTaggedTiddlers(&quot;review&quot;);
		for (var i = 0; i &lt; specialTiddlers.length; i++)
			if (_GTD.tiddlerHasTag(specialTiddlers[i], &quot;gtd&quot;)) {		// only update GTD review tiddlers, as an optimization
				// as a further optimization, we don't refresh tiddlers that aren't actually displayed, and make sure that
				// if they are displayed, they are in view mode, not edit mode
				var el = _GTD.getTiddlerElement(specialTiddlers[i]);
				if (el &amp;&amp; el.getAttribute(&quot;template&quot;) == &quot;reviewViewTemplate&quot;)
					story.refreshTiddler(specialTiddlers[i].title, null, true);
			}

	},
	
	appendProjectActionMarkup: function(projectTiddler, actionTitle, actionContext)
	{
		var actionInsertionPoint = -1, actionLeadin = &quot;&quot;;
		
		var reActionWikitext = &quot;^\\.{2}([^|\\n]+)(?:\\|?)(.*).*$&quot;;
		var reActionMacro = &quot;(.*)&lt;&lt;gtdAction ((?:[^&gt;]|(?:&gt;(?!&gt;)))*)&gt;&gt;.*$&quot;;
		var actionRe = new RegExp(&quot;(&quot; + reActionWikitext + &quot;)|(&quot; + reActionMacro + &quot;)&quot;, &quot;mg&quot;);
		do {
			var formatMatch = actionRe.exec(projectTiddler.text);
			if (formatMatch) {
				actionLeadin = (formatMatch[1] ? &quot;&quot; : formatMatch[5]);
				actionInsertionPoint = actionRe.lastIndex;
			}
		} while(formatMatch);
		
		var actionProto = &quot;\n&quot; + actionLeadin + &quot;&lt;&lt;gtdAction \&quot;&quot; + actionTitle + &quot;\&quot; \&quot;&quot; + actionContext + &quot;\&quot;&gt;&gt;&quot;;
		if (actionInsertionPoint == -1)
			projectTiddler.text += actionProto;
		else
			projectTiddler.text = projectTiddler.text.substring(0, actionInsertionPoint) + actionProto + projectTiddler.text.substr(actionInsertionPoint);
		
		this.tiddlerHasChanged(projectTiddler);
		this.refreshActionViews(projectTiddler);
	},
	
	removeProjectAction: function(projectTiddler, actionTitle)
	{
		var reActionWikitext = &quot;^(\\.{2})[ \\t]*(&quot; + actionTitle + &quot;)[ \\t]*((\\|.*\\n?)|(.*\\n?))&quot;;
		var reActionMacro = &quot;(.*&lt;&lt;gtdAction [\&quot;\']?)(&quot; + actionTitle + &quot;)([\&quot;\']?\\s+(?:[^&gt;]|(?:&gt;(?!&gt;)))*&gt;&gt;.*\\n?)&quot;;
		projectTiddler.text = projectTiddler.text.replace(new RegExp(reActionWikitext, &quot;mg&quot;), &quot;&quot;);
		projectTiddler.text = projectTiddler.text.replace(new RegExp(reActionMacro, &quot;mg&quot;), &quot;&quot;);
		projectTiddler.changed();
		story.refreshTiddler(projectTiddler.title, null, true);
	},
	
	setNextAction: function(project)
	{
		if (project.gtdActions == undefined) project.gtdActions = [];
		project.gtdNextAction = null;
		for (var i = 0; i &lt; project.gtdActions.length; i++)
			if (!project.gtdActions[i].gtdActionDone) {
				project.gtdNextAction = project.gtdActions[i];
				project.gtdProjectDone = false;
				this.toggleTag(project, &quot;done&quot;, project.gtdProjectDone);
				return;
			}
		// if we get here, project is currently complete
		if (project.gtdActions.length &gt; 0) project.gtdProjectDone = true;
		this.toggleTag(project, &quot;done&quot;, project.gtdProjectDone);
	},
	
	clearContextCache: function()
	{
		this.contextCache = null;
	},
	
	getCachedContexts: function()
	{
		if (!this.contextCache) this.contextCache = store.getTaggedTiddlers(&quot;context&quot;);
		return this.contextCache;
	},
	
	renameCachedContext: function(oldName, newName)
	{
		if (this.contextCache) {
			var index = this.contextCache.indexOf(oldName);
			if (index &gt; -1) this.contextCache[index] = newName;
		}
	},
	
	findActionContext: function(action)
	{
		var context = null;
		
		var contexts = this.getCachedContexts();
		for (var i = 0; i &lt; contexts.length; i++)
			if (_GTD.tiddlerHasTag(action, contexts[i].title)) {
				context = contexts[i].title;
				break;
			}
		
		return context;
	},
	
	refreshAllContexts: function()
	{
		var contexts = this.getCachedContexts();
		for (var i = 0; i &lt; contexts.length; i++)
			story.refreshTiddler(contexts[i].title, null, true);
	},
	
	saveWithForcedBackup: function()
	{
		var saveBackups = config.options.chkSaveBackups;
		config.options.chkSaveBackups = true;
		saveChanges();
		config.options.chkSaveBackups = saveBackups;
	},
	
	isNextAction: function(actionTiddler)
	{
		if (actionTiddler.gtdProject &amp;&amp; actionTiddler == actionTiddler.gtdProject.gtdNextAction)
			return true;
		return !actionTiddler.gtdActionDone &amp;&amp; this.tiddlerHasTag(actionTiddler, &quot;floating&quot;);
	},
	
	setReviewUpdate: function()
	{
		window._GTD = this;
		// having a subminute review update is overkill, but it would be nice to have semi-accurate
		// clock, so we can't have it be longer than a minute between updates
		//window.setTimeout('window._GTD.doReviewUpdate()', 60 * 1000);
		var d = new Date();
		window.setTimeout('window._GTD.doReviewUpdate()', (3600 - 60*d.getMinutes() - d.getSeconds()) * 1000);
	},
	
	doReviewUpdate: function()
	{
		this.refreshActionViews(null);
		this.setReviewUpdate();
	},
	
	setLazyAutoSave: function()
	{
		window._GTD = this;
		var interval = parseInt(config.options.txtGTDLazyAutoSaveInterval, 10);
		interval = isNaN(interval) ? 60 : interval.clamp(0, Number.MAX_VALUE);
		window.setTimeout('window._GTD.doLazyAutoSave()', interval * 1000);
	},
	
	doLazyAutoSave: function()
	{
		if (config.options.chkGTDLazyAutoSave &amp;&amp; !config.options.chkAutoSave &amp;&amp; (this.lazyAutoSave &gt; 0 || store.isDirty())) {
			this.lazyAutoSave = 0;
			displayMessage('Autosaving changes...');
			saveChanges();
			if (typeof(gtdAutoSaveHook) == &quot;function&quot;)
				gtdAutoSaveHook();
			window.setTimeout('clearMessage()', 5 * 1000);
		}
		this.setLazyAutoSave();
	},
	
	projectPriority: function(p)
	{
		var maxPriority = _GTD.projectPriorities.length;
		for (var i = 0; i &lt; maxPriority; i++)
			if (_GTD.tiddlerHasTag(p, _GTD.projectPriorities[i])) return (maxPriority - i);
		return 0;
	},
	
	actionSorter: function(a,b)
	{
		// we now have an extended sort function to try and provide a more useful list of actions, esp. in a context view
		// ... the rule now is that project actions appear before non-project actions
		// ... if two actions have projects, and either project is tagged &quot;important&quot;, it will appear first, otherwise actions are alphabetical by project
		// ... if two actions are from the same project, they appear in project action sequence, not alphabetically
		// ... all non-project actions continue to be sorted alphabetically
		
		if (a.gtdProject &amp;&amp; b.gtdProject) {
			var aImportance = _GTD.projectPriority(a.gtdProject), bImportance = _GTD.projectPriority(b.gtdProject);
			if (a.gtdProject == b.gtdProject)
				return (a.gtdProject.gtdActions.indexOf(a) &lt; b.gtdProject.gtdActions.indexOf(b)) ? -1 : +1;
			else if (aImportance != bImportance)
				return (aImportance &gt; bImportance) ? -1 : +1;
			else
				return (a.gtdProject.title &lt; b.gtdProject.title) ? -1 : +1;
		}
		else if (a.gtdProject)
			return -1;	// &quot;a&quot; has a project, &quot;b&quot; doesn't, so &quot;a&quot; comes first
		else if (b.gtdProject)
			return +1;	// &quot;b&quot; has a project, &quot;a&quot; doesn't, so &quot;b&quot; comes first
		else {
			var aImportance = _GTD.tiddlerHasTag(a, &quot;important&quot;), bImportance = _GTD.tiddlerHasTag(b, &quot;important&quot;);
			if (aImportance &amp;&amp; !bImportance)
				return -1;	// &quot;a&quot; is important, &quot;b&quot; is not, so &quot;a&quot; comes first
			else if (bImportance &amp;&amp; !aImportance)
				return +1;	// &quot;b&quot; is important, &quot;a&quot; is not, so &quot;a&quot; comes first
			else
				return (a.title &lt; b.title) ? -1 : +1;
		}
	},
	
	projectSorter: function(a,b) 
	{
		
		var aImportance = _GTD.projectPriority(a), bImportance = _GTD.projectPriority(b);
		if (aImportance != bImportance)
			return (aImportance &gt; bImportance) ? -1 : +1;
		else
			return (a.title &lt; b.title) ? -1 : +1;
	}
};

config.macros.gtdVersion = {}
config.macros.gtdVersion.handler = function(place)
{
	var v = version.extensions.GTDPlugins;
	createTiddlyElement(place, &quot;span&quot;, null, null, v.major + &quot;.&quot; + v.minor + &quot;.&quot; + v.revision + (v.patch ? &quot;.&quot; + v.patch : &quot;&quot;) + (v.beta ? &quot; (beta &quot; + v.beta + &quot;)&quot; : &quot;&quot;));
}

config.macros.list.tagged = {}
config.macros.list.tagged.innerHandler = function(tagList, allTags)
{
	var tiddlers = store.getTaggedTiddlers(tagList[0]);

	if (allTags) {
		var results = [];
		for (var i = 0; i &lt; tiddlers.length; i++) {
			var tiddler = tiddlers[i], hasAllTags = true;
			for (var j = 1; hasAllTags &amp;&amp; j &lt; tagList.length; j++) {
				// hasAllTags &amp;= _GTD.tiddlerHasTag(tiddler, tagList[j]);
				hasAllTags &amp;= (tagList[j].charAt(0) == '-') ? !_GTD.tiddlerHasTag(tiddler, tagList[j].substr(1)) : _GTD.tiddlerHasTag(tiddler, tagList[j])
			}
			if (hasAllTags) results.push(tiddlers[i]);
		}
		return results;
	}
	else {
		for (var i = 1; i &lt; tagList.length; i++) {
			var more = store.getTaggedTiddlers(tagList[i]);
			for (var j = 0; j &lt; more.length; j++)
				tiddlers.pushUnique(more[j]);
		}
		return tiddlers;
	}
}
config.macros.list.tagged.handler = function(params) 
{
	var tags = params[1].readBracketedList();
	if (tags.length == 1) {
		if (config.options[tags[0]] == undefined)
			return store.getTaggedTiddlers(tags[0]);
		else
			return store.getTaggedTiddlers(config.options[tags[0]]);
	}
	else if (tags.length &gt; 1) {
		var allTags = (params[2] == undefined || params[2] == 'all');
		var tiddlers = this.innerHandler(tags, allTags);
		tiddlers.sort(function (a,b) {if(a.title == b.title) return(0); else return (a.title &lt; b.title) ? -1 : +1; });
		return tiddlers;
	}
}

config.macros.gtdActionList = {}
config.macros.gtdActionList.handler = function(place,macroName,params)
{
	var theList = createTiddlyElement(place, &quot;ul&quot;, null, &quot;gtdActionList&quot;);
	var parentTiddlerName = story.findContainingTiddler(place).getAttribute(&quot;tiddler&quot;);
	
	var allActions = (params[1] == &quot;all&quot;);
	var noProjectActions = (params[1] == &quot;noproject&quot;);
	var justProjectActions = (params[1] == &quot;projectonly&quot;);
	var aging = parseInt(config.options.txtGTDActionAging, 10);
	aging = isNaN(aging) ? 0 : aging.clamp(0, Number.MAX_VALUE);
	
	if (params[0] == &quot;*&quot;) {		// review actions for all projects
		var projects = store.getTaggedTiddlers(&quot;project&quot;);
		// do an importance sort on project list first, so they bubble to the top
		projects.sort(_GTD.projectSorter);
		for (var i = 0; i &lt; projects.length; i++) {
			var project = projects[i];
			// filter projects that have been deferred
			if (_GTD.tiddlerHasTag(project, config.options.txtGTDSomedayContext)) continue;
			if (!allActions) {
				//var skipEmptyProject = true;
				//if (project.gtdActions != undefined &amp;&amp; project.gtdActions.length &gt; 0)
				//	for (var k = 0; skipEmptyProject &amp;&amp; k &lt; project.gtdActions.length; k++)
				//		skipEmptyProject = project.gtdActions[k].gtdActionDone;
				//if (skipEmptyProject) continue;
				if (project.gtdActions == undefined || project.gtdActions.length == 0 || project.gtdProjectDone) continue;
			}
			// this will present the actions in the same order as they appear in the project
			var theListItem = createTiddlyElement(theList, &quot;li&quot;, null, &quot;gtdActionListProject&quot;);
			createTiddlyLink(theListItem, project.title, true);
			if (project.gtdActions != undefined &amp;&amp; project.gtdActions.length &gt; 0) {
				var subList = createTiddlyElement(theList, &quot;ul&quot;, null, &quot;gtdActionList&quot;);
				for (var j = 0; j &lt; project.gtdActions.length; j++) {
					var action = project.gtdActions[j];
					// if we are not displaying all actions, filter old completed actions (if specified)
					// if (!allActions &amp;&amp; action.gtdActionDone &amp;&amp; aging &gt; 0 &amp;&amp; _GTD.tiddlerAgeInDays(action) &gt; aging) continue;
					// NEW! we are now filtering all completed actions unless we are displaying all actions
					if (!allActions &amp;&amp; action.gtdActionDone) continue;
					var subListItem = createTiddlyElement(subList, &quot;li&quot;);
					var el = config.macros.gtdAction.createActionElement(subListItem, action, project.title, action.tags);
				}
			}
		}
	}
	
	else if (params[0] == &quot;@&quot;) {	// review actions for all contexts
		var contexts = _GTD.getCachedContexts();
		for (var i = 0; i &lt; contexts.length; i++) {
			var context = contexts[i];
			var actions = config.macros.list.tagged.innerHandler([context.title, &quot;action&quot;], true);
			if (actions.length &gt; 0) {
				var firstAction = true, theListItem, subList;
				actions.sort(_GTD.actionSorter);
				for (var j = 0; j &lt; actions.length; j++) {
					var currentAction = actions[j];
					// special filtering by request...
					if (noProjectActions &amp;&amp; currentAction.gtdProject) continue;
					if (justProjectActions &amp;&amp; typeof(currentAction.gtdProject) == 'undefined') continue;
					// if we are not displaying all actions, filter completed actions and non-next project actions
					if (!allActions &amp;&amp; (currentAction.gtdActionDone || (currentAction.gtdProject &amp;&amp; !_GTD.isNextAction(currentAction)))) continue;
					// filter actions for projects that have been deferred
					if (currentAction.gtdProject &amp;&amp; _GTD.tiddlerHasTag(currentAction.gtdProject, config.options.txtGTDSomedayContext)) continue;
					if (firstAction) {
						theListItem = createTiddlyElement(theList, &quot;li&quot;, null, &quot;gtdActionListContext&quot;);
						createTiddlyLink(theListItem, context.title, true);
						subList = createTiddlyElement(theList, &quot;ul&quot;, null, &quot;gtdActionList&quot;);
						firstAction = false;
					}
					var subListItem = createTiddlyElement(subList, &quot;li&quot;);
					var el = config.macros.gtdAction.createActionElement(subListItem, currentAction, context.title, currentAction.tags);
				}
			}
		}
	}
	
	else {		// actions tagged by current tiddler name, or specified tag list as parameter
		var reviewMode = config.options['chkGTDActionListReviewMode' + escape(parentTiddlerName)];
		if (typeof(reviewMode) == 'undefined') reviewMode = false;
		
		// chain to our &quot;tagged&quot; list macro to get the tiddlers first
		var tags = (params.length == 0 || params[0] == &quot;.&quot; ? [ parentTiddlerName ] : params[0].readBracketedList());
		tags.push(&quot;action&quot;);
		var results = config.macros.list.tagged.innerHandler(tags, true);
		results.sort(_GTD.actionSorter);
		for (var t = 0; t &lt; results.length; t++) {
			var action = results[t];
			// special filtering by request...
			if (noProjectActions &amp;&amp; action.gtdProject) continue;
			if (justProjectActions &amp;&amp; typeof(action.gtdProject) == 'undefined') continue;
			if (action.gtdProject &amp;&amp; _GTD.tiddlerHasTag(action.gtdProject, config.options.txtGTDSomedayContext)) continue;
			// if we are not displaying all actions, filter completed actions and non-next project actions
			if (reviewMode &amp;&amp; !allActions &amp;&amp; (action.gtdActionDone || (action.gtdProject &amp;&amp; !_GTD.isNextAction(action)))) continue;
			// if we are not displaying all actions, filter old completed actions (if specified)
			if (!allActions &amp;&amp; action.gtdActionDone &amp;&amp; aging &gt; 0 &amp;&amp; _GTD.tiddlerAgeInDays(action) &gt; aging) continue;
			var theListItem = createTiddlyElement(theList, &quot;li&quot;);
			var el = config.macros.gtdAction.createActionElement(theListItem, action, parentTiddlerName, action.tags);
		}
	}
}

config.macros.gtdAction = {}
config.macros.gtdAction.createActionElement = function(place, actionTiddler, filterName, tags)
{
	if (typeof(actionTiddler) == &quot;string&quot;) actionTiddler = store.getTiddler(actionTiddler);
	
	var actionElement = createTiddlyElement(place, &quot;span&quot;, null, &quot;gtdActionItem&quot;);
	
	// oddly, we barf when setting the checkbox type on an input if we use createTiddlyElement...
	var cb = document.createElement(&quot;input&quot;);
	cb.setAttribute(&quot;type&quot;, &quot;checkbox&quot;);
	cb.setAttribute(&quot;actionTiddler&quot;, actionTiddler.title);
	cb.onclick = this.onClickDone;
	actionElement.appendChild(cb);
	cb.checked = actionTiddler.gtdActionDone;
	createTiddlyLink(actionElement, actionTiddler.title, true);
	actionElement.className = (actionTiddler.text ? &quot;gtdActionWithContent&quot; : &quot;gtdActionWithoutContent&quot;);
	if (actionTiddler.gtdActionDone) actionElement.className += &quot; gtdCompletedActionItem&quot;;
	if (_GTD.isNextAction(actionTiddler)) actionElement.className += &quot; gtdNextActionItem&quot;;
	
	var filterTags = [], actionTags = [];
	if (actionTiddler.gtdProjectName &amp;&amp; actionTiddler.gtdProjectName.length &gt; 0)
		actionTags.push(actionTiddler.gtdProjectName);
	if (actionTiddler.gtdContextName &amp;&amp; actionTiddler.gtdContextName.length &gt; 0)
		actionTags.push(actionTiddler.gtdContextName);
	for (var i = 0; i &lt; tags.length; i++) actionTags.pushUnique(tags[i]);
	if (filterName &amp;&amp; filterName.length &gt; 0) filterTags.pushUnique(filterName);
	
	actionTags = _GTD.filteredActionTags(actionTags, filterTags);
	if (actionTags.length &gt; 0) {
		createTiddlyText(actionElement, &quot; [ &quot;);
		for (var i = 0; i &lt; actionTags.length; i++) {
			if (i &gt; 0) createTiddlyText(actionElement, &quot;, &quot;);
			createTiddlyLink(actionElement, actionTags[i], true, &quot;actionCrossReference&quot;);
		}
		createTiddlyText(actionElement, &quot; ]&quot;);
	}
	
	return actionElement;
}

config.macros.gtdAction.onClickDone = function(e)
{
	var tiddler = store.getTiddler(this.getAttribute(&quot;actionTiddler&quot;));
	if (tiddler) {
		_GTD.toggleTag(tiddler, &quot;done&quot;, this.checked);
		tiddler.gtdActionDone = this.checked;
		_GTD.tiddlerHasChanged(tiddler);
		_GTD.refreshActionViews(tiddler);
		if (this.checked &amp;&amp; typeof(gtdActionDoneHook) == &quot;function&quot;)
			gtdActionDoneHook(tiddler);
		if (tiddler.gtdActionDone &amp;&amp; tiddler.gtdProject == undefined &amp;&amp; confirm(&quot;This action is not a part of a project. Would you just like to delete it?&quot;)) {
			story.closeTiddler(tiddler.title, false, false);
			store.removeTiddler(tiddler.title);
		}
	}
	return true;
}

config.macros.gtdAction.handler = function(place,macroName,params)
{
	var title = params[0], tags;
	var parentTiddlerName = story.findContainingTiddler(place).getAttribute(&quot;tiddler&quot;);
	var tiddler = store.getTiddler(title);
	if (!tiddler) {
		// we should *never* get here now for project actions, but keep code in case project code
		// trips up, or we use this macro somewhere else
		this.createAction(title, parentTiddlerName, params[1]);
	}
	else
		// use actual tiddler tags, not macro param, in case context changed!
		tags = tiddler.tags;
	var action = this.createActionElement(place, title, parentTiddlerName, tags);
}

config.macros.gtdAction.createAction = function(title, projectTiddlerName, tagParams, extraTags)
{
	// var tags = [&quot;action&quot;, parentTiddler];
	var action, tags = [&quot;action&quot;], fields = {};
	if (_GTD.usingProjectTags)
		tags.push(projectTiddlerName);
	if (typeof(tagParams) == &quot;string&quot;) tags = tags.concat(tagParams.readBracketedList());
	if (typeof(extraTags) == &quot;string&quot;) tags = tags.concat(extraTags.readBracketedList());
	var templateText = store.getTiddlerText(&quot;NewActionTemplate&quot;, config.views.wikified.defaultText.format([title]));
	if (_GTD.usingProjectTags)
		action = store.saveTiddler(title, title, templateText, config.options.txtUserName, new Date(), tags);
	else {
		fields[&quot;gtd.project&quot;] = projectTiddlerName;
		action = store.saveTiddler(title, title, templateText, config.options.txtUserName, new Date(), tags, fields);
	}
	return action;
}

config.macros.gtdActionCompleted = {}
config.macros.gtdActionCompleted.handler = function(place,macroName,params)
{
	if (!readOnly) {
		var title = story.findContainingTiddler(place).getAttribute(&quot;tiddler&quot;);
		var tiddler = store.getTiddler(title);
		// oddly, we barf when setting the checkbox type on an input if we use createTiddlyElement...
		var cb = document.createElement(&quot;input&quot;);
		cb.setAttribute(&quot;type&quot;, &quot;checkbox&quot;);
		cb.setAttribute(&quot;actionTiddler&quot;, title);
		cb.onclick = this.onClickDone;
		place.appendChild(cb);
		cb.checked = tiddler.gtdActionDone;
	}
}

config.macros.gtdActionCompleted.onClickDone = function(e)
{
	var tiddler = store.getTiddler(this.getAttribute(&quot;actionTiddler&quot;));
	if (tiddler) {
		_GTD.toggleTag(tiddler, &quot;done&quot;, this.checked);
		tiddler.gtdActionDone = this.checked;
		_GTD.tiddlerHasChanged(tiddler);
		_GTD.refreshActionViews(tiddler);
		if (this.checked &amp;&amp; typeof(gtdActionDoneHook) == &quot;function&quot;)
			gtdActionDoneHook(tiddler);
		if (tiddler.gtdActionDone &amp;&amp; tiddler.gtdProject == undefined &amp;&amp; confirm(&quot;This action is not a part of a project. Would you just like to delete it?&quot;)) {
			story.closeTiddler(tiddler.title, false, false);
			store.removeTiddler(tiddler.title);
		}
	}
	return true;
}

config.macros.gtdToggleTag = {}
config.macros.gtdToggleTag.handler = function(place,macroName,params)
{
	if (!readOnly) {
		var title = story.findContainingTiddler(place).getAttribute(&quot;tiddler&quot;);
		var tiddler = store.getTiddler(title);
		// oddly, we barf when setting the checkbox type on an input if we use createTiddlyElement...
		var cb = document.createElement(&quot;input&quot;);
		cb.setAttribute(&quot;type&quot;, &quot;checkbox&quot;);
		cb.setAttribute(&quot;tiddler&quot;, title);
		cb.setAttribute(&quot;toggledTag&quot;, params[0]);
		cb.onclick = this.onClickDone;
		place.appendChild(cb);
		cb.checked = _GTD.tiddlerHasTag(tiddler, params[0]);
	}
}

config.macros.gtdToggleTag.onClickDone = function(e)
{
	var tiddler = store.getTiddler(this.getAttribute(&quot;tiddler&quot;));
	if (tiddler) {
		_GTD.toggleTag(tiddler, this.getAttribute(&quot;toggledTag&quot;), this.checked);
		_GTD.tiddlerHasChanged(tiddler);
		if (_GTD.tiddlerHasTag(tiddler, &quot;action&quot;))
			_GTD.refreshActionViews(tiddler);
		else
			// we need a broad notification here, not just refreshActionViews
			store.notify(tiddler.title, true);
	}
	return true;
}

config.macros.gtdToggleState = {}
config.macros.gtdToggleState.handler = function(place,macroName,params)
{
	var title = story.findContainingTiddler(place).getAttribute(&quot;tiddler&quot;);
	var tiddler = store.getTiddler(title);
	// oddly, we barf when setting the checkbox type on an input if we use createTiddlyElement...
	var cb = document.createElement(&quot;input&quot;);
	cb.setAttribute(&quot;type&quot;, &quot;checkbox&quot;);
	cb.setAttribute(&quot;tiddler&quot;, title);
	var state = params[0] + escape(title);
	cb.setAttribute(&quot;stateName&quot;, state);
	cb.onclick = this.onClickDone;
	place.appendChild(cb);
	cb.checked = config.options[state];
}

config.macros.gtdToggleState.onClickDone = function(e)
{
	var tiddler = store.getTiddler(this.getAttribute(&quot;tiddler&quot;));
	if (tiddler) {
		var state = this.getAttribute(&quot;stateName&quot;);
		config.options[state] = this.checked;
		saveOptionCookie(state);
		story.refreshTiddler(tiddler.title, null, true);
	}
	return true;
}

config.macros.importUpdates = { 
	importMode: &quot;updates&quot;,
	buttonTitle: &quot;Update&quot;, 
	buttonHelp: &quot;Click here to update the application&quot;,
	missingHelperMessage: &quot;This functionality depends on the LoadTiddlersPlugin, which is missing. Please import the plugin from TiddlyTools.&quot;,
	preUpdateMessage: &quot;Once the download is finished, you will need to reload your document to complete the update. In order to allow you to review the update tiddlers, this will not be done automatically. \n\nClick \&quot;OK\&quot; start the update.&quot;,
	postUpdateMessage: &quot;Please remember, you will need to save and reload your document to complete the update. In order to allow you to review the update tiddlers, this will not be done automatically.&quot;
}
config.macros.importUpdates.handler = function(place, macroName, params)
{
	var mode = params[1] ? params[1] : this.importMode;
	var title = params[2] ? params[2] : this.buttonTitle;
	var prompt = params[3] ? params[3] : this.buttonHelp;
	var button = createTiddlyButton(place, title, prompt, this.onClickUpdate);
	button.setAttribute(&quot;updateSource&quot;, params[0]);
	button.setAttribute(&quot;importMode&quot;, mode);
	if (params.length &gt; 4) button.setAttribute(&quot;importExtras&quot;, params.slice(4).join(&quot; &quot;));
}

config.macros.importUpdates.onClickUpdate = function(e)
{
	if (config.macros.loadTiddlers == undefined || version.extensions.LoadTiddlersPlugin == undefined) {
		alert(config.macros.importUpdates.missingHelperMessage);
		return;
	}
	if (!confirm(config.macros.importUpdates.preUpdateMessage))
		return;
	var importParams = [ this.getAttribute(&quot;importMode&quot;), this.getAttribute(&quot;updateSource&quot;) ];
	var importExtras = this.getAttribute(&quot;importExtras&quot;);
	if (importExtras) importParams = importParams.concat(importExtras.split(&quot; &quot;));
	// force a saveChanges with backup before the update
	_GTD.saveWithForcedBackup();
	// chain to the loadTiddlers macro
	config.macros.loadTiddlers.handler(this, &quot;loadTiddlers&quot;, importParams);
	// ensure that relevant release notes are displayed on first launch
	config.options.chkGTDReleaseNotes = true;
	saveOptionCookie(&quot;chkGTDReleaseNotes&quot;);
	// do *not* cause a browser navigation
	return false;
}

config.macros.gtdArchive = {}
config.macros.gtdArchive.handler = function(place, macroName, params)
{
	var archiveAction = params.length &gt; 0 ? params[0] : &quot;archive&quot;
	var btn = createTiddlyButton(place, archiveAction, &quot;&quot;, this.onClick);
	btn.setAttribute(&quot;archiveAction&quot;, archiveAction);
}

config.macros.gtdArchive.onClick = function(e)
{
	var warning = &quot;Are you sure you want to %0 all %1 projects and actions?&quot;;
	var status = &quot;There were %0 project(s) and %1 action(s) %2d.&quot;;
	var archiveAction = this.getAttribute(&quot;archiveAction&quot;);
	
	var projectCount = 0, actionCount = 0;
	
	if (archiveAction == &quot;archive&quot;) {
		if (confirm(warning.format([archiveAction, &quot;completed&quot;]))) {
			clearMessage();
			var projects = store.getTaggedTiddlers(&quot;project&quot;);
			for (var i = 0; i &lt; projects.length; i++) {
				var project = projects[i];
				if (project.gtdActions == undefined || project.gtdActions.length == 0) continue;
				var projectComplete = true;
				for (var j = 0; projectComplete &amp;&amp; j &lt; project.gtdActions.length; j++)
					projectComplete = project.gtdActions[j].gtdActionDone;
				if (!projectComplete) continue;
				// if we get here, all project actions are done, so archive project
				story.closeTiddler(project.title, false, false);
				_GTD.tiddlerSwapTag(project, &quot;project&quot;, &quot;project-archive&quot;);
				_GTD.tiddlerHasChanged(project, false);
				projectCount++;
				for (j = 0; j &lt; project.gtdActions.length; j++) {
					story.closeTiddler(project.gtdActions[j].title, false, false);
					_GTD.tiddlerSwapTag(project.gtdActions[j], &quot;action&quot;, &quot;action-archive&quot;);
					_GTD.tiddlerHasChanged(project.gtdActions[j], false);
					actionCount++;
				}
			}
			var actions = store.getTaggedTiddlers(&quot;action&quot;);
			for (i = 0; i &lt; actions.length; i++) {
				var action = actions[i];
				if (action.gtdActionDone &amp;&amp; !action.gtdProject) {
					story.closeTiddler(action.title, false, false);
					_GTD.tiddlerSwapTag(action, &quot;action&quot;, &quot;action-archive&quot;);
					_GTD.tiddlerHasChanged(action, false);
					actionCount++;
				}
			}
			displayMessage(status.format([projectCount, actionCount, archiveAction]));
			var saveClearMessage = clearMessage;
			clearMessage = function() {};
			if (config.options.chkAutoSave) saveChanges();
			clearMessage = saveClearMessage;
			store.notify(null, true);
		}
	}
	
	else if (archiveAction == &quot;unarchive&quot;) {
		if (confirm(warning.format([archiveAction, &quot;archived&quot;]))) {
			clearMessage();
			var projects = store.getTaggedTiddlers(&quot;project-archive&quot;);
			for (var i = 0; i &lt; projects.length; i++) {
				var project = projects[i];
				story.closeTiddler(project.title, false, false);
				_GTD.tiddlerSwapTag(project, &quot;project-archive&quot;, &quot;project&quot;);
				_GTD.tiddlerHasChanged(project, false);
				projectCount++;
			}
			var actions = store.getTaggedTiddlers(&quot;action-archive&quot;);
			for (i = 0; i &lt; actions.length; i++) {
				var action = actions[i];
				story.closeTiddler(action.title, false, false);
				_GTD.tiddlerSwapTag(action, &quot;action-archive&quot;, &quot;action&quot;);
				_GTD.tiddlerHasChanged(action, false);
				actionCount++;
			}
			displayMessage(status.format([projectCount, actionCount, archiveAction]));
			var saveClearMessage = clearMessage;
			clearMessage = function() {};
			if (config.options.chkAutoSave) saveChanges();
			clearMessage = saveClearMessage;
			store.notify(null, true);
		}
	}
	
	else if (archiveAction == &quot;purge&quot;) {
		if (confirm(warning.format([archiveAction, &quot;archived&quot;]))) {
			clearMessage();
			_GTD.saveWithForcedBackup();
			var projects = store.getTaggedTiddlers(&quot;project-archive&quot;);
			for (var i = 0; i &lt; projects.length; i++) {
				var project = projects[i];
				story.closeTiddler(project.title, false, false);
				store.removeTiddler(project.title);
				projectCount++;
			}
			var actions = store.getTaggedTiddlers(&quot;action-archive&quot;);
			for (i = 0; i &lt; actions.length; i++) {
				var action = actions[i];
				story.closeTiddler(action.title, false, false);
				store.removeTiddler(action.title);
				actionCount++;
			}
			displayMessage(status.format([projectCount, actionCount, archiveAction]));
			var saveClearMessage = clearMessage;
			clearMessage = function() {};
			if (config.options.chkAutoSave) saveChanges();
			clearMessage = saveClearMessage;
			store.notify(null, true);
		}
	}
	else
		alert(&quot;That archiving action is not supported&quot;);
}

config.formatters.push(
	{
	name: &quot;gtdAction&quot;,
	match: &quot;^\\.{2}.*&quot;,
	lookahead: &quot;^\\.{2}([^|]*)(?:\\|?)(.*)&quot;,
	handler: function(w)
		{
			var lookaheadRegExp = new RegExp(this.lookahead,&quot;g&quot;);
			var lookaheadMatch = lookaheadRegExp.exec(w.matchText)
			if (lookaheadMatch) {
				var params = [ lookaheadMatch[1].trim() ];
				if (lookaheadMatch[2].trim().length &gt; 0) params.push(lookaheadMatch[2].trim());
				config.macros.gtdAction.handler(w.output, &quot;gtdAction&quot;, params);
			}
		}
	}
);

config.commands.newAction = { text: &quot;action&quot;, tooltip: &quot;Create a new action for this context&quot;, hideReadOnly: true };
config.commands.newAction.handler = function(event, src, context)
{
	var d = new Date();
	var newActionTitle = d.formatString(&quot;New Action hh:0mm:0ss&quot;);
	if (!store.tiddlerExists(newActionTitle)) {
		var tiddler = store.createTiddler(newActionTitle);
		var templateText = store.getTiddlerText(&quot;NewActionTemplate&quot;, config.views.wikified.defaultText.format([newActionTitle]));
		tiddler.assign(newActionTitle, templateText, config.options.txtUserName, new Date(), [ &quot;action&quot;, context ]);
		
		story.displayTiddler(null, newActionTitle, DEFAULT_EDIT_TEMPLATE);
		story.focusTiddler(newActionTitle, &quot;title&quot;);
	}
	return false;
}

config.commands.newProjectAction = { text: &quot;action&quot;, tooltip: &quot;Create a new action for this project&quot;, hideReadOnly: true };
config.commands.newProjectAction.handler = function(event, src, project)
{
	var d = new Date();
	var newActionTitle = d.formatString(&quot;New Action hh:0mm:0ss&quot;);
	if (!store.tiddlerExists(newActionTitle)) {
		var defaultContext = config.options.txtGTDUnfiledContext;
		_GTD.appendProjectActionMarkup(store.getTiddler(project), newActionTitle, defaultContext);
		
		var tiddler = store.createTiddler(newActionTitle);
		var templateText = store.getTiddlerText(&quot;NewActionTemplate&quot;, config.views.wikified.defaultText.format([newActionTitle]));
		var tags = [&quot;action&quot;], fields = {};
		if (_GTD.usingProjectTags)
			tags.push(project);
		tags.push(defaultContext);
		if (_GTD.usingProjectTags)
			tiddler.assign(newActionTitle, templateText, config.options.txtUserName, new Date(), tags);
		else {
			fields[&quot;gtd.project&quot;] = project;
			tiddler.assign(newActionTitle, templateText, config.options.txtUserName, new Date(), tags, new Date(), fields);
		}
		
		story.displayTiddler(null, newActionTitle, DEFAULT_EDIT_TEMPLATE);
		story.focusTiddler(newActionTitle, &quot;title&quot;);
	}
	return false;
}

config.commands.changeContext = { text: &quot;context&quot;, tooltip: &quot;Change context of this action&quot;, hideReadOnly: true, popupNone: &quot;There are no contexts&quot; };
config.commands.changeContext.handler = function(event,src,title)
{
	var popup = Popup.create(src);
	if (popup) {
		var contexts = _GTD.getCachedContexts();
		var tiddler = store.getTiddler(title);
		var currentContext = _GTD.findActionContext(tiddler);
		if (!currentContext) currentContext = '';
		
		var c = false;
		for (var i = 0; i &lt; contexts.length; i++)
			if (contexts[i].title != currentContext) {
				var button = createTiddlyButton(createTiddlyElement(popup, &quot;li&quot;), contexts[i].title, '', this.onClickContext);
				button.setAttribute(&quot;actionTiddler&quot;, title);
				button.setAttribute(&quot;oldContext&quot;, currentContext);
				button.setAttribute(&quot;newContext&quot;, contexts[i].title);
				c = true;
			}
			
		if (!c)
			createTiddlyText(createTiddlyElement(popup, &quot;li&quot;, null, &quot;disabled&quot;), this.popupNone);
	}
	
	Popup.show(popup, false);
	event.cancelBubble = true;
	if (event.stopPropagation) event.stopPropagation();
	// do *not* cause a browser navigation
	return false;
}

config.commands.changeContext.onClickContext = function(e)
{
	var tiddler = store.getTiddler(this.getAttribute(&quot;actionTiddler&quot;));
	if (tiddler) {
		var contextChanged = false;
		var oldContext = this.getAttribute(&quot;oldContext&quot;);
		var newContext = this.getAttribute(&quot;newContext&quot;);
		if (oldContext.length == 0)
			contextChanged = (tiddler.tags.push(newContext) &gt; 0);
		else
			contextChanged = _GTD.tiddlerSwapTag(tiddler, oldContext, newContext);
		
		if (contextChanged) {
			tiddler.gtdContextName = newContext;
			_GTD.setExtendedValue(tiddler, &quot;gtd.context&quot;, newContext);
			_GTD.tiddlerHasChanged(tiddler);
			_GTD.refreshActionViews(tiddler);
			// be sure to refresh old context as well...
			story.refreshTiddler(oldContext, null, true);
		}
	}
	// do *not* cause a browser navigation
	return false;
}

config.commands.changeProject = { text: &quot;project&quot;, tooltip: &quot;Change project of this action&quot;, hideReadOnly: true, popupNone: &quot;There are no projects&quot; };
config.commands.changeProject.handler = function(event,src,title)
{
	var popup = Popup.create(src);
	if (popup) {
		var projects = store.getTaggedTiddlers(&quot;project&quot;);
		var tiddler = store.getTiddler(title);
		var currentProject = (tiddler.gtdProject ? tiddler.gtdProject.title : '');
		
		var c = false;
		for (var i = 0; i &lt; projects.length; i++)
			if (projects[i].title != currentProject) {
				var button = createTiddlyButton(createTiddlyElement(popup, &quot;li&quot;), projects[i].title, '', this.onClickProject);
				button.setAttribute(&quot;actionTiddler&quot;, title);
				button.setAttribute(&quot;oldProject&quot;, currentProject);
				button.setAttribute(&quot;newProject&quot;, projects[i].title);
				c = true;
			}
			
		if (!c)
			createTiddlyText(createTiddlyElement(popup, &quot;li&quot;, null, &quot;disabled&quot;), this.popupNone);
	}
	
	Popup.show(popup, false);
	event.cancelBubble = true;
	if (event.stopPropagation) event.stopPropagation();
	// do *not* cause a browser navigation
	return false;
}

config.commands.changeProject.onClickProject = function(e)
{
	var tiddler = store.getTiddler(this.getAttribute(&quot;actionTiddler&quot;));
	if (tiddler) {
		var oldProject = this.getAttribute(&quot;oldProject&quot;);
		var newProject = this.getAttribute(&quot;newProject&quot;);
		
		if (oldProject.length &gt; 0)
			_GTD.removeProjectAction(tiddler.gtdProject, tiddler.title)
		if (_GTD.usingProjectTags)
			_GTD.tiddlerSwapTag(tiddler, oldProject, newProject);
		else
			_GTD.setExtendedValue(tiddler, &quot;gtd.project&quot;, newProject);
		_GTD.appendProjectActionMarkup(store.getTiddler(newProject), tiddler.title, tiddler.gtdContextName);
		
		_GTD.tiddlerHasChanged(tiddler);
		_GTD.refreshActionViews(tiddler);
	}
	// do *not* cause a browser navigation
	return false;
}

config.commands.deleteAction = { text: &quot;delete&quot;, tooltip: &quot;Delete this action&quot;, hideReadOnly: true, warning: &quot;Are you sure you want to delete '%0'?&quot;, altwarning: &quot;Are you sure you want to delete '%0'? The action will also be removed from project '%1'.&quot; };
config.commands.deleteAction.handler = function(event, src, title)
{
	var tiddler = store.getTiddler(title);
	var ok = (tiddler.gtdProject ? confirm(this.altwarning.format([title, tiddler.gtdProject.title])) : confirm(this.warning.format([title])));
	if (ok) {
		if (tiddler.gtdProject) _GTD.removeProjectAction(tiddler.gtdProject, title);
		store.removeTiddler(title);
		story.closeTiddler(title,true,event.shiftKey || event.altKey);
		if (config.options.chkAutoSave)
			saveChanges();
	}
	
	return false;
}

config.commands.deleteContext = { text: &quot;delete&quot;, tooltip: &quot;Delete this context&quot;, hideReadOnly: true, warning: &quot;Are you sure you want to delete '%0'? All associated actions will be tagged as 'unfiled'.&quot; };
config.commands.deleteContext.handler = function(event, src, title)
{
	if (confirm(this.warning.format([title]))) {
		store.suspendNotifications();
		this.unlinkActions(title);
		// force a rebuild of our context cache
		_GTD.clearContextCache();
		store.resumeNotifications();
		store.removeTiddler(title);
		story.closeTiddler(title,true,event.shiftKey || event.altKey);
		if (config.options.chkAutoSave)
			saveChanges();
	}
	
	return false;
}

config.commands.deleteContext.unlinkActions = function(contextTitle)
{
	var tiddlers = config.macros.list.tagged.innerHandler([contextTitle, &quot;action&quot;], true);
	for (var i = 0; i &lt; tiddlers.length; i++) {
		var tiddler = tiddlers[i];
		_GTD.tiddlerSwapTag(tiddler, contextTitle, config.options.txtGTDUnfiledContext);
		_GTD.tiddlerHasChanged(tiddler, false);
		// context removal will do view notification...
	}
}

config.commands.archiveProject = { text: &quot;archive&quot;, tooltip: &quot;Archive this project&quot;, hideReadOnly: true, warning: &quot;Are you sure you want to archive '%0'?&quot;, noarchive: &quot;This project is %0 and will not be archived.&quot; };
config.commands.archiveProject.handler = function(event, src, title)
{
	if (confirm(this.warning.format([title]))) {
		var project = store.getTiddler(title);
		if (project.gtdActions == undefined || project.gtdActions.length == 0) {
			alert(this.noarchive.format([&quot;empty&quot;]));
			return;
		}
		var projectComplete = true;
		for (var j = 0; projectComplete &amp;&amp; j &lt; project.gtdActions.length; j++)
			projectComplete = project.gtdActions[j].gtdActionDone;
		if (!projectComplete) {
			alert(this.noarchive.format([&quot;incomplete&quot;]));
			return;
		}
		// if we get here, all project actions are done, so archive project
		story.closeTiddler(project.title, false, false);
		_GTD.tiddlerSwapTag(project, &quot;project&quot;, &quot;project-archive&quot;);
		_GTD.tiddlerHasChanged(project, false);
		for (j = 0; j &lt; project.gtdActions.length; j++) {
			story.closeTiddler(project.gtdActions[j].title, false, false);
			_GTD.tiddlerSwapTag(project.gtdActions[j], &quot;action&quot;, &quot;action-archive&quot;);
			_GTD.tiddlerHasChanged(project.gtdActions[j], false);
		}
		store.notify(null, true);
		if (config.options.chkAutoSave)
			saveChanges();
	}

	return false;
}

config.commands.deleteProject = { text: &quot;delete&quot;, tooltip: &quot;Delete this project&quot;, hideReadOnly: true, warning: &quot;Are you sure you want to delete '%0'? All associated actions will no longer be bound to this (or any) project.&quot; };
config.commands.deleteProject.handler = function(event, src, title)
{
	if (confirm(this.warning.format([title]))) {
		store.suspendNotifications();
		this.unlinkActions(title);
		store.resumeNotifications();
		store.removeTiddler(title);
		story.closeTiddler(title,true,event.shiftKey || event.altKey);
		if (config.options.chkAutoSave)
			saveChanges();
	}
	
	return false;
}

config.commands.deleteProject.unlinkActions = function(projectTitle)
{
	// var tiddlers = config.macros.list.tagged.innerHandler([projectTitle, &quot;action&quot;], true);
	var project = store.getTiddler(projectTitle);
	for (var i = 0; i &lt; project.gtdActions.length; i++) {
		var tiddler = project.gtdActions[i];
		tiddler.gtdProject = null;
		tiddler.gtdProjectName = null;
		if (_GTD.usingProjectTags)
			tiddler.tags.splice(tiddler.tags.indexOf(projectTitle), 1);
		else {
			_GTD.setExtendedValue(tiddler, &quot;gtd.project&quot;, null);
			_GTD.setExtendedValue(tiddler, &quot;gtd.projectindex&quot;, null);
		}
		_GTD.tiddlerHasChanged(tiddler, false);
		// project removal will do view notification...
	}
}

config.commands.deleteProjectAll = { text: &quot;delete all&quot;, tooltip: &quot;Delete this project and its actions&quot;, hideReadOnly: true, warning: &quot;Are you sure you want to delete '%0' and all its associated actions?&quot; };
config.commands.deleteProjectAll.handler = function(event, src, title)
{
	if (confirm(this.warning.format([title]))) {
		store.suspendNotifications();
		this.deleteActions(title);
		store.resumeNotifications();
		store.removeTiddler(title);
		story.closeTiddler(title,true,event.shiftKey || event.altKey);
		if (config.options.chkAutoSave)
			saveChanges();
	}
	
	return false;
}

config.commands.deleteProjectAll.deleteActions = function(projectTitle)
{
	// var tiddlers = config.macros.list.tagged.innerHandler([projectTitle, &quot;action&quot;], true);
	var project = store.getTiddler(projectTitle);
	for (var i = 0; i &lt; project.gtdActions.length; i++) {
		var tiddler = project.gtdActions[i].title;
		store.removeTiddler(tiddler);
		story.closeTiddler(tiddler, true, false);
		// project removal will do view notification...
	}
}

config.commands.projectify = { text: &quot;projectify&quot;, tooltip: &quot;Convert this action to a project&quot;, hideReadOnly: true, warning: &quot;Are you sure you want to convert '%0' to a project?&quot; };
config.commands.projectify.handler = function(event, src, title)
{
	if (confirm(this.warning.format([title]))) {
		var tiddler = store.getTiddler(title);
		if (tiddler.gtdProject) _GTD.removeProjectAction(tiddler.gtdProject, title);
		tiddler.tags = [ &quot;project&quot; ];
		_GTD.tiddlerHasChanged(tiddler, true);
		// we need a broad notification here, not just refreshActionViews
		store.notify(title, true);
	}
	
	return false;
}

// *** ***/
// *** These are overrides to core TiddlyWiki functionality ***
// *** ***/

Tiddler.prototype._GTDInheritedChanged = Tiddler.prototype.changed;
Tiddler.prototype.changed = function()
{
	this._GTDInheritedChanged();
	
	// Note that this is called both as part of normal tiddler changes AND as a part
	// of the initial TW loading process from DIVs...
	
	if (_GTD.tiddlerHasTag(this, &quot;project&quot;)) {
		// (re)build the in-memory ordered action list
		this.gtdActions = [];
		this.gtdNextAction = null;
		if (this.text) {
			var reActionWikitext = &quot;^\\.{2}([^|\\n]+)(?:\\|?)(.*)&quot;;
			var reActionMacro = &quot;&lt;&lt;gtdAction ((?:[^&gt;]|(?:&gt;(?!&gt;)))*)&gt;&gt;&quot;;
			var actionRe = new RegExp(&quot;(&quot; + reActionWikitext + &quot;)|(&quot; + reActionMacro + &quot;)&quot;, &quot;mg&quot;);
			do {
				var formatMatch = actionRe.exec(this.text);
				if (formatMatch) {
					var macroParams = (formatMatch[1] ? null : formatMatch[5].readMacroParams());
					// note that for the &quot;..&quot; notation, we are trimming up action titles and contexts
					var actionTiddlerName = (formatMatch[1] ? formatMatch[2].trim() : macroParams[0]);
					var actionTiddler = store.getTiddler(actionTiddlerName);
					if (!actionTiddler) {
						var actionTags = (formatMatch[1] ? formatMatch[3].trim() : macroParams[1]);
						var extraTags = (formatMatch[1] ? '' : macroParams[2]);
						actionTiddler = config.macros.gtdAction.createAction(actionTiddlerName, this.title, actionTags, extraTags);
					}
					if (actionTiddler) {
						actionTiddler.gtdProject = this;
						if (this.gtdNextAction == null &amp;&amp; !_GTD.tiddlerHasTag(actionTiddler, &quot;done&quot;))
							this.gtdNextAction = actionTiddler;
						this.gtdActions.push(actionTiddler);
						_GTD.setExtendedValue(actionTiddler, &quot;gtd.projectindex&quot;, this.gtdActions.length - 1);
						// handle project renaming in action
						if (actionTiddler.gtdProjectName &amp;&amp; actionTiddler.gtdProjectName != this.title) {
							if (_GTD.usingProjectTags)
								_GTD.tiddlerSwapTag(actionTiddler, actionTiddler.gtdProjectName, this.title);
							else
								_GTD.setExtendedValue(actionTiddler, &quot;gtd.project&quot;, this.title);
							// action view won't get updated through any other refresh mechanism, so
							story.refreshTiddler(actionTiddler.title, null, true);
						}
						actionTiddler.gtdProjectName = this.title;
					}
				}
			} while(formatMatch);
		}
	}
	
	else if (_GTD.tiddlerHasTag(this, &quot;context&quot;)) {
		if (this.gtdContextName == undefined)
			this.gtdContextName = this.title;
		else if (this.gtdContextName != this.title) {
			// propagate renamed context to affected actions
			store.suspendNotifications();
			var results = config.macros.list.tagged.innerHandler([ this.gtdContextName, &quot;action&quot;], true);
			for (var t = 0; t &lt; results.length; t++) {
				_GTD.tiddlerSwapTag(results[t], this.gtdContextName, this.title);
				results[t].gtdContextName = this.title;
				_GTD.setExtendedValue(results[t], &quot;gtd.context&quot;, this.title);
				// action view won't get updated through any other refresh mechanism, so
				//story.refreshTiddler(results[t].title, null, true);
			}
			// because the store is not yet updated, we need to manipulate the context cache directly
			_GTD.renameCachedContext(this.gtdContextName, this.title);
			this.gtdContextName = this.title;
			// we need a broad notification here, not just refreshActionViews
			store.resumeNotifications();
			store.notify(null, true);
		}
	}
	
	else if (_GTD.tiddlerHasTag(this, &quot;action&quot;)) {
		if (this.gtdActionName == undefined)
			this.gtdActionName = this.title;
		else if (this.gtdActionName != this.title &amp;&amp; this.gtdProject) {
			// ugh...dig into related project and update the wiki code to use new action name
			var reActionWikitext = &quot;^(\\.{2}[ \\t]*)(&quot; + this.gtdActionName + &quot;)(([ \\t]*\\|.*\\n?)|(\\n?))&quot;;
			var reActionMacro = &quot;(&lt;&lt;gtdAction [\&quot;\']?)(&quot; + this.gtdActionName + &quot;)([\&quot;\']?\\s+(?:[^&gt;]|(?:&gt;(?!&gt;)))*&gt;&gt;)&quot;;
			this.gtdProject.text = this.gtdProject.text.replace(new RegExp(reActionWikitext, &quot;mg&quot;), &quot;$1&quot; + this.title + &quot;$3&quot;);
			this.gtdProject.text = this.gtdProject.text.replace(new RegExp(reActionMacro, &quot;mg&quot;), &quot;$1&quot; + this.title + &quot;$3&quot;);
			this.gtdActionName = this.title;
		}
		this.gtdActionDone = _GTD.tiddlerHasTag(this, &quot;done&quot;);
		this.gtdContextName = _GTD.findActionContext(this);
		_GTD.setExtendedValue(this, &quot;gtd.context&quot;, this.gtdContextName);
		// reset the next action on the associated project
		if (this.gtdProject) _GTD.setNextAction(this.gtdProject);
	}
}

Story.prototype.chooseTemplateForTiddler = function(title,template)
{
	// This override to core TW functionality is used to provide tag-based view and edit templates. The
	// basic idea is that the tiddler is scanned for its tags and, depending on whether we are opening a
	// tiddler in &quot;view&quot; or &quot;edit&quot; mode, a corresponding 'tag + &quot;ViewTemplate&quot;' or 'tag + &quot;EditTemplate&quot;'
	// tiddler is searched for. If it exists, it is used instead of the default templates.
	
	if (!template)
		template = DEFAULT_VIEW_TEMPLATE;

	// before reverting to default behaviour, check to see if a tag-based template exists
	if (template == DEFAULT_VIEW_TEMPLATE || template == DEFAULT_EDIT_TEMPLATE) {
		if (this.tagBasedTemplateCache == undefined) this.tagBasedTemplateCache = new Array();
		var templateRoot = (template == DEFAULT_VIEW_TEMPLATE ? &quot;ViewTemplate&quot; : &quot;EditTemplate&quot;);
		var tiddler = store.getTiddler(title);
		if (tiddler) {
			for (var i = 0; i &lt; tiddler.tags.length; i++) {
				var tag = tiddler.tags[i];
				var tagTemplate = tag + templateRoot;
				var tagCacheId = tag + template;
				// first check our cache to see if we have seen this template before
				if (this.tagBasedTemplateCache[tagCacheId] != undefined) {
					// make sure template still exists
					if (store.tiddlerExists(this.tagBasedTemplateCache[tagCacheId])) {
						template = this.tagBasedTemplateCache[tagCacheId];
						break;
					}
					else
						delete this.tagBasedTemplateCache[tagCacheId];
				}
				// go to the store to see if template exists
				if (store.tiddlerExists(tagTemplate)) {
					template = tagTemplate;
					this.tagBasedTemplateCache[tagCacheId] = tagTemplate;
					break;
				}
			}
		}
	}
	
	if (template == DEFAULT_VIEW_TEMPLATE || template == DEFAULT_EDIT_TEMPLATE)
		template = config.tiddlerTemplates[template];
	return template;
}

// Clint Checketts' IE first-child patch, version 1.1, http://www.checkettsweb.com/tw/gtd_tiddlywiki.htm#GiveFirstTiddlerClassPatch

Story.prototype.closeTiddlerIEFirstChild = Story.prototype.closeTiddler;
Story.prototype.closeTiddler = function(title,animate,slowly) {
	var tiddler = document.getElementById(this.idPrefix + title);
	// we need to test to ensure tiddler is actually open
	if (tiddler) {
		var storyArea = tiddler.parentNode;
		if ((this.idPrefix + title) == storyArea.firstChild.id){
			removeClass(storyArea.firstChild,&quot;IEFirstChild&quot;);
			// this next line is redundant, since it is looked after at the end of this function
			// if (storyArea.firstChild.nextSibling) addClass(storyArea.firstChild.nextSibling,&quot;IEFirstChild&quot;);
		}
		story.closeTiddlerIEFirstChild(title,animate,slowly);
		if (storyArea.firstChild) addClass(storyArea.firstChild,&quot;IEFirstChild&quot;);
	}
}

Story.prototype.displayTiddlerIEFirstChild = Story.prototype.displayTiddler;
Story.prototype.displayTiddler = function(srcElement,tiddler,template,animate,unused,customFields,toggle,animationSrc) {
	var storyArea = document.getElementById(this.container);
	if (storyArea.firstChild) removeClass(storyArea.firstChild,&quot;IEFirstChild&quot;);
	story.displayTiddlerIEFirstChild(srcElement,tiddler,template,animate,unused,customFields,toggle,animationSrc);
	addClass(storyArea.firstChild,&quot;IEFirstChild&quot;);
}

_GTD.initialize();

//}}}
</pre>
</div>
<div title="GTDStyleSheet" modifier="TomO" created="200603212219" modified="200705290122" tags="gtd">
<pre>/***
!GTD specific styles
***/

/*{{{*/
/* how annoying is that big header anyway?! */
.headerForeground, .headerShadow {
 padding-top: 1em;
}

/* the tagging popup really gets in the way so push it off to the side */
.tagging { float: right; }

/* this unbullets actions in the actionList macro */
ul.gtdActionList { list-style-type: none; }
li.gtdActionListProject, li.gtdActionListContext { margin-top: 1.0em; }

.gtdCompletedActionItem { text-decoration: line-through; }
.gtdNextActionItem { border-bottom: 1px solid red; }
.gtdActionWithContent a { font-weight: bold; }
.gtdActionWithoutContent a { font-weight: normal; }

a.actionCrossReference { color: #228B22; }
a.actionCrossReference:hover { color: white; }

/* necessary bits copied from enhanced stylesheet to render properly without it */
#mainMenu {
 font-size: 1em;
 text-align: left;
 width: 12em;
}

#mainMenu * {
 font-size: 1em;
 font-weight: normal;
 padding: 0; margin: 0; border: 0;
}

#mainMenu ul {
 list-style: none;
 margin-bottom: 10px;
}

#mainMenu li {
 text-indent: 1em;
}

#mainMenu li li {
 text-indent: 1.5em;
}

#mainMenu li li li {
 text-indent: 2em;
}

#mainMenu li li li li {
 text-indent: 2.5em;
}

#mainMenu a.button, #mainMenu a.tiddlyLink, #mainMenu a.externalLink {
 display: block; margin: 0;
}

table.review { width: 98%; }
.review tr { vertical-align: top; }

/*}}}*/

/***
!Imported 3x5 printing styles
//adapted from the work of Clint Checketts, http://www.checkettsweb.com/tw/gtd_tiddlywiki.htm //
***/

/*{{{*/

@media print {
#mainMenu, #sidebar, #messageArea {display: none !important;}
#displayArea {margin: 1em 1em 0em 1em;}


/* LAYOUT ELEMENTS ========================================================== */
*
{
 margin: 0;
 padding: 0;
}

#contentWrapper
{
 margin: 0;
 width: 100%;
 position: static;
}

body {
 background: #fff;
 color: #000;
 font-size: 6.2pt;
 font-family: &quot;Lucida Grande&quot;, &quot;Bitstream Vera Sans&quot;, Helvetica, Verdana, Arial, sans-serif;
 /* we don't want any unnecessary output */
 border: none;
}

img {
 max-width: 2.2in;
 max-height: 4.3in;
}

#header, #side_container, #storeArea, #copyright, #floater, #messageArea, .save_accesskey, .site_description, #saveTest, .toolbar, .header, .footer, .tagging, .tagged
{
 display: none;
}

#tiddlerDisplay, #displayArea
{
 display: inline;
}

.tiddler {
 margin: 0 0 2em 0;
 border: none;
 page-break-before: always;
 font-size: 200%;
 /* IF YOU DO NOT USE INDEX CARDS, USE THIS */
 font-size: 100%;
}

.tiddler:first-child {
 page-break-before: avoid;
}

/* this relies on Clint's IE first-child patch */
.IEFirstChild {
 page-break-before: auto;
}

.title {
 font-size: 1.6em;
 font-weight: bold;
 margin-bottom: .3em;
 padding: .2em 0;
 border-bottom: 1px dotted #000;
}

p, blockquote, ul, li, ol, dt, dd, dl, table
{
 margin: 0 0 .3em 0;
}

h1, h2, h3, h4, h5, h6
{
 margin: .2em 0;
} 

h1
{
 font-size: 1.5em;
}

h2
{
 font-size: 1.3em;
}

h3
{
 font-size: 1.25em;
}

h4
{
 font-size: 1.15em;
}

h5
{
 font-size: 1.1em;
}

blockquote
{
 margin: .6em;
 padding-left: .6em;
 border-left: 1px solid #ccc;
}

ul
{
 list-style-type: circle;
}

li
{
 margin: .1em 0 .1em 2em;
 line-height: 1.4em; 
}

table
{
 border-collapse: collapse;
 font-size: 1em;
}

td, th
{
 border: 1px solid #999;
 padding: .2em;
}

hr {
 border: none;
 border-top: dotted 1px #777;
 height: 1px;
 color: #777;
 margin: .6em 0;
}
}
/*}}}*/

/***
!Imported styles for calendar plugin
***/

/*{{{*/

#mainMenu .calendar {
 width: 100%;
 background-color: transparent !important;
}

#mainMenu .calendar, #mainMenu .calendar tr, #mainMenu .calendar td, #mainMenu .calendar a {
}


/*}}}*/
</pre>
</div>
<div title="GTDTWStyleSheet" modifier="TomO" created="200604161508" modified="200806282306" tags="gtd">
<pre>/***
!Layout Rules /%==============================================%/
***/
/*{{{*/

body {
 /* this is required for proper layout on IE, for some reason... */
 _position: static;
}

.tagClear {
 /* this, too, is a necessary IE hack... */
 _margin-top: 10em; 
 _clear: both;
}

.headerForeground, .headerShadow {
 padding-top: 1em;
}

.tiddler {
 margin: 0 0 0.9em 0;
 padding-bottom: 1em;
}

#mainMenu {
 width: 16em;
 font-size: 1em;
 text-align: left;
 padding-top: 0.5em;
}

#mainMenu * {
 font-size: 1em;
 font-weight: normal;
 padding: 0; margin: 0; border: 0;
}

#mainMenu ul {
 list-style: none;
 margin-bottom: 10px;
}

#mainMenu li {
 text-indent: 1em;
}

#mainMenu a.button, #mainMenu a.tiddlyLink, #mainMenu a.externalLink {
 display: block; margin: 0;
}

#displayArea {
 margin-left: 19em; margin-top: 0;
}

.toolbar .button {
 margin-left: 4px;
}

/*}}}*/

/***
!Generic Rules /%==============================================%/
***/
/*{{{*/
body {
 background: #464646;
 color: #000;
}

h1,h2,h3,h4,h5 {
 color: #000;
 background: #eee;
}

/*}}}*/
/***
!Header /%==================================================%/
***/
/*{{{*/
.header {
 background: #000;
}

.headerForeground {
 color: #cf6;
}

.headerForeground a {
 font-weight: normal;
 color: #cf6;
}

/* ??? what is up when you specify a site title colour in IE ??? */
/* .siteTitle { color: red; } */

/*}}}*/
/***
!General tabs /%=================================================%/
***/
/*{{{*/

.tabSelected {
 color: #fff;
 background: #960;
 border: none;
}

.tabUnselected {
 color: #fff;
 background: #660;
}

.tabContents {
 color: #004;
 background: #960;
 border: none;
}

.tabContents .button, .tabContents a {
 border: none;
 color: #fff;
}

.tabContents a:hover, .tabset a:hover {
 background: #000;
}

/* make nested tab areas look different */
.tabContents .tabSelected, .tabContents .tabContents {
 background: #700;
 color: #fff;
}

.tabContents .tabContents {
 color: #eeb;
}

/*}}}*/
/***
!Main Menu /%=================================================%/
***/
/*{{{*/
#mainMenu {
 background: #700;
 color: #fff;
 border-right: 3px solid #500;
}

#mainMenu * {
 color: #fff;
}

#mainMenu a.button, #mainMenu a.tiddlyLink, #mainMenu a.externalLink {
 border: none;
 border-bottom: 1px solid #500;
 border-top: 1px solid #900;
}

#mainMenu a:hover,
#mainMenu a.button:hover {
 background-color: #b00;
 color: #fff;
}

/*}}}*/
/***
!Sidebar options /%=================================================%/
~TiddlyLinks and buttons are treated identically in the sidebar and slider panel
***/
/*{{{*/
#sidebar {
 color: #000;
 background: #eeb;
 border-right: 3px solid #bb8;
 border-bottom: 3px solid #520;
}

#sidebarOptions .sliderPanel {
 background: #fff;
}

#sidebarOptions .sliderPanel a {
 border: none;
 color: #700;
}

#sidebarOptions .sliderPanel a:hover {
 color: #fff;
 background: #700;
}

#sidebarOptions .sliderPanel a:active {
 color: #700;
 background: #fff;
}

#sidebarOptions a {
 color: #700;
 border: none;
}

#sidebarOptions a:hover, #sidebarOptions a:active {
 color: #fff;
 background: #700;
}

/*}}}*/
/***
!Message Area /%=================================================%/
***/
/*{{{*/
#messageArea {
 border-right: 3px solid #da1;
 border-bottom: 3px solid #a80;
 background: #ffe72f;
 color: #014;
}

/*}}}*/
/***
!Popup /%=================================================%/
***/
/*{{{*/
.popup {
 background: #cf6;
 border: none;
}

.popup hr {
 color: #000;
}

.popup li.disabled {
 color: #666;
 background: #cf6;
}

.popup li a, .popup li a:visited {
 color: #000;
 border: 1px outset #cf6;
 background: #cf6;
}

.popup li a:hover {
 color: #000;
 border: 1px outset #cf6;
 background: #ef9;
}
/*}}}*/
/***
!Tiddler Display /%=================================================%/
***/
/*{{{*/
.tiddler {
 background: #fff;
 border-right: 3px solid #aaa;
 border-bottom: 3px solid #555;
}

.title {
 color: #900;
}

.selected .toolbar a {
 color: #000;
}

.toolbar .button {
 background: #eeb /*#cf6*/;
 border: 1px outset #eeb /*#cf6*/;
}

.selected .toolbar .button:hover {
 background: #700 /*#ef9*/;
 color: #fff;
}

#mainMenu .calendar { border: 1px solid white; }
#mainMenu .calendar, #mainMenu .calendar tr, #mainMenu .calendar td, #mainMenu .calendar a {
}

/*}}}*/

/***
!Additional print overrides for fancy style /%==============================================%/
***/
/*{{{*/

@media print {

.tiddler {
 /* get rid of our fancy tiddler outline */
 border: none;
}

}
/*}}}*/
</pre>
</div>
<div title="HTML Slidy Maker - Macro" modifier="ImaiMasanobu" created="200612111400" modified="200906240540" tags="systemConfig" systemconfig="" html="" slidy="" maker="" -="" macro_backup="" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" server.page.revision="200612160809" creator="ImaiMasanobu" changecount="1">
<pre>/***
|Name|HTML Slidy Maker Macro|
|Created by|potto|
|Location| http://www.potto.client.jp/|
|Version|$Revision: 0.319 $|
|Requires|TiddlyWiki 2.1.3 |

&lt;&lt;HTMLSlidyMaker&gt;&gt;

!!説明(Description)
　指定記事のリストに従って、記事をHTML Slidy 形式に変換する。&quot;HTMLSlidyMakerList&quot;に記事のタイトルを列記し、&quot;GO&quot;ボタンを押すと変換され、TiddlyWikiのあるフォルダと同一な場所に作成されます。
　&quot;HTMLSlidyMaker list&quot;に記事列記用のタイトル、&quot;Save file name&quot;に保存ファイル名を入力すると変更が可能。記事のtagsはHTML Slidyのclass名を入れる。tagsが指定されていない場合は&quot;slide&quot;classが適用される。

!!設置
　以下の設定とコードをコピーし、tagsに&quot;systemConfig&quot;を設定します。 &quot; SideBarOptions &quot;などの記事に{{{&lt;&lt;HTMLSlidyMaker&gt;&gt;}}}を記述することで設置が完了します。{{{&lt;&lt;HTMLSlidyMaker&gt;&gt;}}}は必ず１つだけの設置として下さい。自動で以下のタイトル名の記事が作成されます。必要に応じて内容を編集して下さい。&quot;HTMLSlidyMakerConfig&quot;にタイトルとコピーライトの表示などがありますので，ご自分で編集して下さい。

|タイトル|内容|
|[[HTMLSlidyMaker_head]]|HTML Slidy のヘッダ部分|
|[[HTMLSlidyMaker_footer]]|HTML Slidy のフッタ部分|
|[[HTMLSlidyMakerList]]|出力記事リスト|
|[[HTMLSlidyMakerConfig]]|設定ファイル|

!!HTML Slydyについて
　HTML SlydyはW3C製のHTMLプレゼンテーションツールです。IEやFirefoxなどのブラウザで動作します。

公式サイト：[[W3C Slide show Tools|http://www.w3.org/Talks/Tools/]]

!!備考
　生成された&quot;HTML Slydy&quot;はテキストエディタで編集が可能です。ブラウザのFirefoxなどで生成したものが日本語が見やすく編集が楽にできます。生成にはFirefoxをお勧めします。

!!設定

***/
/*{{{*/
config.HTMLSlidyMaker ={};
config.HTMLSlidyMaker.settings = {
    HTMLSlidyMakerList:'HTMLSlidyMakerList',
    SaveName          :'HTMLSlidyMaker.html',
    SaveMsg           :&quot;HTML Slidy ファイルの生成します。よろしいですか?\n file name is &quot;,
    HtmlHead          :'HTMLSlidyMaker_head',
    HtmlFooter        :'HTMLSlidyMaker_footer',
    SavedMsg           :'HTML Slidy saved',
    ErroSaveMsg       :'ERRO:HTML Slidy not saved',
    user              :'HTMLSlidyMaker',
    title         :'title',
    UserConfig        :'HTMLSlidyMakerConfig',
	
	/* -----------------------------------------------------*/
    Mainformdat   : '&lt;form mime=&quot;text/plain&quot; name=&quot;HTMLSlidyMaker&quot;&gt;'
               + '&lt;p&gt;HTMLSlidyMaker list&lt;br/&gt;&lt;input type=&quot;text&quot; name=&quot;slist&quot;&gt;&lt;/p&gt;'
               + '&lt;p&gt;title&lt;br/&gt;&lt;input type=&quot;text&quot; name=&quot;stitle&quot;&gt;&lt;/p&gt;'
               + '&lt;p&gt;Save file name&lt;br/&gt;&lt;input type=&quot;text&quot; name=&quot;sfile&quot;&gt;&lt;/p&gt;'
               + '&lt;p&gt;&lt;input type=&quot;button&quot; name=&quot;go&quot; value=&quot;GO&quot; onclick=&quot;config.macros.HTMLSlidyMaker.go();&quot;&gt;&lt;/p&gt;'
               + '&lt;/form&gt;',
	
	HtmlHeaddat   : '&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;' +'\n'
               + '&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&gt;'+'\n'
               + '&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; lang=&quot;en&quot; xml:lang=&quot;en&quot;&gt;'+'\n'
               + '&lt;head&gt;'+'\n'
               + '&lt;meta http-equiv=&quot;Content-type&quot; content=&quot;text/html; charset=utf-8&quot;&gt;'+'\n'
               + '&lt;title&gt;[[HTMLSlidyMakerConfig::title]]&lt;/title&gt;'+'\n'
               + '&lt;meta name=&quot;copyright&quot; content=&quot;[[HTMLSlidyMakerConfig::Copyright]]&quot; /&gt;'+'\n'
               + '&lt;meta name=&quot;font-size-adjustment&quot; content=&quot;-1&quot; /&gt;' + '\n'
               + '&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;screen, projection, print&quot; '+'\n'
               + ' href=&quot;http://www.w3.org/Talks/Tools/Slidy/slidy.css&quot; /&gt; '+'\n'
               + ' &lt;script src=&quot;http://www.w3.org/Talks/Tools/Slidy/slidy.js&quot; '+'\n'
               + ' type=&quot;text/javascript&quot;&gt;&lt;/script&gt; '+'\n'
               + ' &lt;style type=&quot;text/css&quot;&gt; '+'\n'
               + ' &lt;!-- your custom style rules --&gt;'+'\n' 
               + ' &lt;/style&gt; '+'\n'
               + ' &lt;/head&gt;'+'\n'
               + '&lt;body&gt;'+'\n',
	
	HtmlFooterdat : '&lt;/body&gt;'+ '\n'
               + '&lt;/html&gt;',
	
	UserConfigdat : &quot;title:title&quot; +&quot;\n&quot;
               +&quot;Copyright:Copyright &amp;#169; 2006 your copyright notice&quot; +&quot;\n&quot;
               +&quot;HTMLSlidyMakerList:HTMLSlidyMakerList&quot;+&quot;\n&quot;
               +&quot;SaveName:HTMLSlidyMaker.html&quot; + &quot;\n&quot;
               

	};

/*}}}*/
/***
!!Code

***/
/*{{{*/
config.macros.HTMLSlidyMaker={};
config.macros.HTMLSlidyMaker.handler=function(place,macroName,params){
    this.setup();
    this.loadSetting();
    var hsmConfig = config.HTMLSlidyMaker.settings;
    var e = createTiddlyElement(place,&quot;div&quot;,null,null,&quot;&quot;);

    e.innerHTML = hsmConfig.Mainformdat;

    document.HTMLSlidyMaker.slist.value =hsmConfig.HTMLSlidyMakerList;
    document.HTMLSlidyMaker.sfile.value =hsmConfig.SaveName;
    document.HTMLSlidyMaker.stitle.value =hsmConfig.title;
};


config.macros.HTMLSlidyMaker.go=function(){

    var hsmConfig = config.HTMLSlidyMaker.settings;
    hsmConfig.HTMLSlidyMakerList =  document.HTMLSlidyMaker.slist.value;
    hsmConfig.SaveName =  document.HTMLSlidyMaker.sfile.value;
    hsmConfig.title = document.HTMLSlidyMaker.stitle.value;

	if (!confirm(hsmConfig.SaveMsg + &quot; &quot;+ hsmConfig.SaveName )) return;

    /*Save User Setting*/
    this.saveSetting(&quot;HTMLSlidyMakerList&quot;);
    this.saveSetting(&quot;SaveName&quot;);
    this.saveSetting(&quot;title&quot;);
    saveChanges();

    
    /*var head      = store.getTiddlerText(HSMconfig.headHTML); /**/
    var head      = store.getRecursiveTiddlerText(hsmConfig.HtmlHead,null,10);
    var footer    = store.getTiddlerText(hsmConfig.HtmlFooter);
    var s = head;
    
    var params = store.getTiddlerText(hsmConfig.HTMLSlidyMakerList).parseParams(&quot;name&quot;,null,false); //load HTMLSlidyMakerList
    var names = params[0][&quot;name&quot;];
    if(names ==undefined)return; //There is no Tiddlers.

    /* Load tiddlers */
    for (i = 0; i &lt; names.length; i++) {
    	var tiddler = store.fetchTiddler(names[i]);
		if(!tiddler)continue;

        text =this.changebr(this.wikifyHTML(names[i]));
        
        var tags = tiddler.getTags().htmlEncode();    //set tags
        if(tags==&quot;&quot;) tags=&quot;slide&quot;;
        s +='&lt;div class=&quot;' + tags + '&quot;&gt;\n' + text + &quot;&lt;/div&gt;\n&quot;;
     }
    
    /*footer*/
    s +=footer;

   /*---------------------------------*/
   /* save                            */
   /*---------------------------------*/
    // Get the URL of the document
    var originalPath = document.location.toString();
    // Check we were loaded from a file URL
    if(originalPath.substr(0,5) != &quot;file:&quot;){
        alert(config.messages.notFileUrlError);
        if(store.tiddlerExists(config.messages.saveInstructions))
            story.displayTiddler(null,config.messages.saveInstructions);
        return;
     }
    var localPath = getLocalPath(originalPath);

    if((p = localPath.lastIndexOf(&quot;/&quot;)) != -1)
            SlidySavePath = localPath.substr(0,p) + &quot;/&quot; +hsmConfig.SaveName;
        else if((p = localPath.lastIndexOf(&quot;\\&quot;)) != -1)
            SlidySavePath = localPath.substr(0,p) + &quot;\\&quot; +hsmConfig.SaveName;
        else
            SlidySavePath = localPath + &quot;.&quot;+hsmConfig.SaveName;
        
    var SlidySave = saveFile(SlidySavePath,convertUnicodeToUTF8(s));

    if(SlidySave)
        displayMessage(hsmConfig.SavedMsg,&quot;file://&quot; + SlidySavePath);
        else
            alert(hsmConfig.ErroSaveMsg);

};

config.macros.HTMLSlidyMaker.changebr =function(s){
        var text = s;
        text = text.replace(/&lt;/g,&quot;\n&lt;&quot;);
        text = text.replace(/&gt;/g,&quot;&gt;\n&quot;);
        text = text.replace(/^\n+/gm,&quot;&quot;);
        if(config.browser.isIE)
        text = text.replace(/\n/gm,&quot;\r&quot;);
        return text;
};

config.macros.HTMLSlidyMaker.wikifyHTML =function(title){
    if(store.tiddlerExists(title) || store.isShadowTiddler(title)){
        var wikifier = new Wikifier(store.getTiddlerText(title),formatter,null,store.getTiddler(title));
        var e = createTiddlyElement(document.body,&quot;div&quot;);
        e.style.display = &quot;none&quot;;
        wikifier.subWikify(e);
        var text = e.innerHTML;
        e.parentNode.removeChild(e);
        return text;
    }else{
        return &quot;&quot;;
    }
};


config.macros.HTMLSlidyMaker.setup=function(){
    var hsmConfig = config.HTMLSlidyMaker.settings;
    var htitle = hsmConfig.HtmlHead;
    var ftitle = hsmConfig.HtmlFooter;
    var ltitle = hsmConfig.HTMLSlidyMakerList;
    var ctitle = hsmConfig.UserConfig;
    var s;

    if(!store.tiddlerExists(htitle)){
		this.SaveTiddler(htitle,hsmConfig.HtmlHeaddat);
		saveChanges();
	 }
     if(!store.tiddlerExists(ftitle)){
		this.SaveTiddler(ftitle,hsmConfig.HtmlFooterdat);
		saveChanges();
	 }
    if(!store.tiddlerExists(ltitle)){
		this.SaveTiddler(ltitle,&quot;&quot;);
		saveChanges();
	 }
     if(!store.tiddlerExists(ctitle)){
		this.SaveTiddler(ctitle,hsmConfig.UserConfigdat);
		saveChanges();
	 }
};

config.macros.HTMLSlidyMaker.loadSetting=function(){
    var title = config.HTMLSlidyMaker.settings.UserConfig;
    var tiddler = store.fetchTiddler(title);
    if(!tiddler)return;
    
    var moptions = store.getTiddlerText(title).split(&quot;\n&quot;);
        for(var i=0; i&lt;moptions.length; i++){
            var p = moptions[i].indexOf(&quot;:&quot;);
                if(p != -1){
                    var name = moptions[i].substr(0,p).trim();
                    var value = moptions[i].substr(p+1).trim();
                     config.HTMLSlidyMaker.settings[name] = value;
                }
        }

};

config.macros.HTMLSlidyMaker.saveSetting=function(name){
    var hsmConfig = config.HTMLSlidyMaker.settings;
    
    var moptions =new Array();
    if (store.tiddlerExists(hsmConfig.UserConfig))
                moptions  = store.getTiddlerText(hsmConfig.UserConfig).split(&quot;\n&quot;);
    if (name) {
        var s = name + &quot;:&quot; + (hsmConfig[name].toString());

        for (var i=0; i&lt;moptions.length; i++) {
                        var p = moptions[i].indexOf(&quot;:&quot;);
                        if(p != -1) {
                            var key = moptions[i].substr(0,p).trim();
                            if (key == name) {
                                moptions.splice(i, 1);
                                break;
                            }
                        }
                        
        }
        moptions.push(s);
        var text = moptions.join(&quot;\n&quot;);
        this.SaveTiddler(hsmConfig.UserConfig,text);
    }

};



config.macros.HTMLSlidyMaker.SaveTiddler=function(title,s){
 var tiddler = store.fetchTiddler(title);
 var HSMconfig = config.HTMLSlidyMaker.settings;
 var now = new Date();

 var created;
 if(tiddler){
    created = tiddler.created;
     store.deleteTiddler(title);
 } else {
     tiddler = new Tiddler();
     created = now;
 }
 tiddler.set(title,s,HSMconfig.user,now,&quot;&quot;,created);
 store.addTiddler(tiddler);
};
/*}}}*/


/***
!!履歴
2006年11月11日 version 0.1 誕生
2006年11月11日 version 0.2 自動セットアップ機能を搭載
2006年11月13日 version 0.3 設定を記事に書き込む様に変更
***/
</pre>
</div>
<div title="HTMLSlidyMakerConfig" modifier="HTMLSlidyMaker" created="200612120740" modified="200906240820" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" server.page.revision="200612160809" creator="ImaiMasanobu" changecount="3">
<pre>HTMLSlidyMakerList:HTMLSlidyMakerList
SaveName:twitterclone.html
title:TwitterCloneの説明</pre>
</div>
<div title="HTMLSlidyMakerList" modifier="ImaiMasanobu" created="200612110136" modified="200906240737" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" server.page.revision="200612120155" creator="ImaiMasanobu" changecount="8">
<pre>[[slide.タイトル]]
[[slide.はじめに]]
[[slide.データモデル]]
[[slide.処理モデル]]
[[slide.Grailsとは]]
[[slide.Grails Plugin]]
[[slide.実装例-DBアクセス]]
[[slide.実装例-非同期処理]]
[[slide.オマケ的要素]]
[[slide.拡張性]]
[[slide.おわりに]]
</pre>
</div>
<div title="HTMLSlidyMaker_footer" modifier="ImaiMasanobu" created="200612110217" modified="200906240218" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" server.page.revision="200612111004" creator="ImaiMasanobu" changecount="2">
<pre>&lt;/body&gt;
&lt;/html&gt;</pre>
</div>
<div title="HTMLSlidyMaker_head" modifier="ImaiMasanobu" created="200612120813" modified="200906240250" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" server.page.revision="200612130628" creator="ImaiMasanobu" changecount="5">
<pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; lang=&quot;en&quot; xml:lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta http-equiv=&quot;Content-type&quot; content=&quot;text/html; charset=utf-8&quot;&gt;
&lt;title&gt;[[HTMLSlidyMakerConfig::title]]&lt;/title&gt;
&lt;meta name=&quot;copyright&quot; content=&quot;[[HTMLSlidyMakerConfig::Copyright]]&quot; /&gt;
&lt;meta name=&quot;font-size-adjustment&quot; content=&quot;-1&quot; /&gt;
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;screen, projection, print&quot; href=&quot;./slide.css&quot; /&gt; 
  &lt;script src=&quot;http://www.w3.org/Talks/Tools/Slidy/slidy.js&quot; 
   charset=&quot;utf-8&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt; 
 &lt;style type=&quot;text/css&quot;&gt; 
 &lt;!-- your custom style rules --&gt;
 &lt;/style&gt; 
 &lt;/head&gt;
&lt;body&gt;
</pre>
</div>
<div title="LoadTiddlersPlugin" modifier="ELSDesignStudios" created="200703220614" modified="200810271456" tags="ImportExportPackage systemConfig">
<pre>/***
|Name|LoadTiddlersPlugin|
|Source|http://www.TiddlyTools.com/#LoadTiddlersPlugin|
|Documentation|http://www.TiddlyTools.com/#LoadTiddlersPluginInfo|
|Version|3.6.3|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1|
|Type|plugin|
|Requires||
|Overrides||
|Options|##Configuration|
|Description|macro for automated updates or one-click installations of tiddlers from remote sources|
!!!!!Documentation
&gt;see [[LoadTiddlersPluginInfo]]
!!!!!Configuration
&lt;&lt;&lt;
__password-protected server settings //(optional, if needed)//:__
&gt;username: &lt;&lt;option txtRemoteUsername&gt;&gt; password: &lt;&lt;option txtRemotePassword&gt;&gt;
&gt;{{{usage: &lt;&lt;option txtRemoteUsername&gt;&gt; &lt;&lt;option txtRemotePassword&gt;&gt;}}}
&gt;''note: these settings are also used by [[ExternalTiddlersPlugin]] and [[ImportTiddlersPlugin]]''
&lt;&lt;&lt;
!!!!!Revisions
&lt;&lt;&lt;
2008.10.27 [3.6.3] in doImport(), fixed Safari bug by replacing static Array.concat(...) with new Array().concat(...)
|please see [[LoadTiddlersPluginInfo]] for additional revision details|
2005.07.20 [1.0.0] Initial Release
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.LoadTiddlersPlugin= {major: 3, minor: 6, revision: 3, date: new Date(2008,10,27)};

config.macros.loadTiddlers = {
	label: &quot;&quot;,
	tip: &quot;add/update tiddlers from '%0'&quot;,
	lockedTag: &quot;noReload&quot;,	// if existing tiddler has this tag value, don't overwrite it, even if inbound tiddler is newer
	askMsg: &quot;Please enter a local path/filename or a remote URL&quot;,
	openMsg: &quot;Opening %0&quot;,
	openErrMsg: &quot;Could not open %0 - error=%1&quot;,
	readMsg: &quot;Read %0 bytes from %1&quot;,
	foundMsg: &quot;Found %0 tiddlers in %1&quot;,
	nochangeMsg: &quot;'%0' is up-to-date... skipped.&quot;,
	lockedMsg: &quot;'%0' is tagged '%1'... skipped.&quot;,
	skippedMsg: &quot;skipped (cancelled by user)&quot;,
	loadedMsg: &quot;Loaded %0 of %1 tiddlers from %2&quot;,
	reportTitle: &quot;ImportedTiddlers&quot;,
	warning: &quot;Warning!!  Processing '%0' as a systemConfig (plugin) tiddler may produce unexpected results! Are you sure you want to proceed?&quot;,
	handler: function(place,macroName,params) {
		var label=(params[0] &amp;&amp; params[0].substr(0,6)=='label:')?params.shift().substr(6):this.label;
		var tip=(params[0] &amp;&amp; params[0].substr(0,7)=='prompt:')?params.shift().substr(7):this.tip;
		var filter=&quot;updates&quot;;
		if (params[0] &amp;&amp; (params[0]=='all' || params[0]=='new' || params[0]=='changes' || params[0]=='updates'
			|| params[0].substr(0,8)=='tiddler:' || params[0].substr(0,4)=='tag:'))
			filter=params.shift();
		var src=params.shift(); if (!src || !src.length) return; // filename is required
		var quiet=(params[0]==&quot;quiet&quot;); if (quiet) params.shift();
		var ask=(params[0]==&quot;confirm&quot;); if (ask) params.shift();
		var force=(params[0]==&quot;force&quot;); if (force) params.shift();
		var init=(params[0]==&quot;init&quot;); if (init) params.shift();
		var noreport=(params[0]==&quot;noreport&quot;); if (noreport) params.shift();
		this.newTags=[]; if (params[0]) this.newTags=params; // any remaining params are used as &quot;autotags&quot;
		if (label.trim().length) {
			// link triggers load tiddlers from another file/URL and then applies filtering rules to add/replace tiddlers in the store
			createTiddlyButton(place,label.format([src.replace(/%20/g,&quot; &quot;)]),tip.format([src.replace(/%20/g,&quot; &quot;)]), function() {
				if (src==&quot;ask&quot;) src=prompt(this.askMsg);
				config.macros.loadTiddlers.loadFile(src,config.macros.loadTiddlers.doImport,{quiet:quiet,ask:ask,filter:filter,force:force,init:init,noreport:noreport});
			})
		}
		else {
			// load tiddlers from another file/URL and then apply filtering rules to add/replace tiddlers in the store
			if (src==&quot;ask&quot;) src=prompt(this.askMsg);
			config.macros.loadTiddlers.loadFile(src,config.macros.loadTiddlers.doImport,{quiet:quiet,ask:ask,filter:filter,force:force,init:init,noreport:noreport});
		}
	},
	loadFile: function(src,callback,params) {
		var quiet=params.quiet;
		if (src==undefined || !src.length) return null; // filename is required
		if (!quiet) clearMessage();
		if (!quiet) displayMessage(this.openMsg.format([src.replace(/%20/g,&quot; &quot;)]));
		if (src.substr(0,5)!=&quot;http:&quot; &amp;&amp; src.substr(0,5)!=&quot;file:&quot;) { // if not a URL, read from local filesystem
			var txt=loadFile(src);
			if (!txt) { // file didn't load, might be relative path.. try fixup
				var pathPrefix=document.location.href;  // get current document path and trim off filename
				var slashpos=pathPrefix.lastIndexOf(&quot;/&quot;); if (slashpos==-1) slashpos=pathPrefix.lastIndexOf(&quot;\\&quot;); 
				if (slashpos!=-1 &amp;&amp; slashpos!=pathPrefix.length-1) pathPrefix=pathPrefix.substr(0,slashpos+1);
				src=pathPrefix+src;
				if (pathPrefix.substr(0,5)!=&quot;http:&quot;) src=getLocalPath(src);
				var txt=loadFile(src);
			}
			if (!txt) { // file still didn't load, report error
				if (!quiet) displayMessage(this.openErrMsg.format([src.replace(/%20/g,&quot; &quot;),&quot;(unknown)&quot;]));
			} else {
				if (!quiet) displayMessage(this.readMsg.format([txt.length,src.replace(/%20/g,&quot; &quot;)]));
				if (callback) callback(true,params,convertUTF8ToUnicode(txt),src,null);
			}
		} else {
			var name=config.options.txtRemoteUsername; var pass=config.options.txtRemotePassword;
			var x=doHttp(&quot;GET&quot;,src,null,null,name,pass,callback,params,null);
		}
	},
	readTiddlersFromHTML: function(html) {
		// for TW2.2+
		if (TiddlyWiki.prototype.importTiddlyWiki!=undefined) {
			var remoteStore=new TiddlyWiki();
			remoteStore.importTiddlyWiki(html);
			return remoteStore.getTiddlers(&quot;title&quot;);	
		}
	},
	doImport: function(status,params,html,src,xhr) {
		var quiet=params.quiet;
		var ask=params.ask;
		var filter=params.filter;
		var force=params.force;
		var init=params.init;
		var noreport=params.noreport;
		var tiddlers = config.macros.loadTiddlers.readTiddlersFromHTML(html);
		var count=tiddlers?tiddlers.length:0;
		var querypos=src.lastIndexOf(&quot;?&quot;); if (querypos!=-1) src=src.substr(0,querypos);
		if (!quiet) displayMessage(config.macros.loadTiddlers.foundMsg.format([count,src.replace(/%20/g,&quot; &quot;)]));
		store.suspendNotifications();
		var count=0;
		if (tiddlers) for (var t=0;t&lt;tiddlers.length;t++) {
			var inbound = tiddlers[t];
			var theExisting = store.getTiddler(inbound.title);
			if (inbound.title==config.macros.loadTiddlers.reportTitle)
				continue; // skip &quot;ImportedTiddlers&quot; history from the other document...
			if (theExisting &amp;&amp; theExisting.tags.contains(config.macros.loadTiddlers.lockedTag)) {
				if (!quiet) displayMessage(config.macros.loadTiddlers.lockedMsg.format([theExisting.title,config.macros.loadTiddlers.lockedTag]));
				continue; // skip existing tiddler if tagged with 'noReload'
			}
			// apply the all/new/changes/updates filter (if any)
			if (filter &amp;&amp; filter!=&quot;all&quot;) {
				if ((filter==&quot;new&quot;) &amp;&amp; theExisting) // skip existing tiddlers
					continue;
				if ((filter==&quot;changes&quot;) &amp;&amp; !theExisting) // skip new tiddlers
					continue;
				if ((filter.substr(0,4)==&quot;tag:&quot;) &amp;&amp; inbound.tags.indexOf(filter.substr(4))==-1) // must match specific tag value
					continue;
				if ((filter.substr(0,8)==&quot;tiddler:&quot;) &amp;&amp; inbound.title!=filter.substr(8)) // must match specific tiddler name
					continue;
				if (!force &amp;&amp; store.tiddlerExists(inbound.title) &amp;&amp; ((theExisting.modified.getTime()-inbound.modified.getTime())&gt;=0)) {
					var msg=config.macros.loadTiddlers.nochangeMsg;
					if (!quiet&amp;&amp;msg.length) displayMessage(msg.format([inbound.title]));
					continue;
				}
			}
			// get confirmation if required
			if (ask &amp;&amp; !confirm((theExisting?&quot;Update&quot;:&quot;Add&quot;)+&quot; tiddler '&quot;+inbound.title+&quot;'\nfrom &quot;+src.replace(/%20/g,&quot; &quot;)+&quot;\n\nOK to proceed?&quot;))
				{ tiddlers[t].status=config.macros.loadTiddlers.skippedMsg; continue; }
			// DO IT!
			var tags=new Array().concat(inbound.tags,config.macros.loadTiddlers.newTags);
	                store.saveTiddler(inbound.title, inbound.title, inbound.text, inbound.modifier, inbound.modified, tags, inbound.fields, true, inbound.created);
	                store.fetchTiddler(inbound.title).created = inbound.created; // force creation date to imported value - needed for TW2.1.3 or earlier
			tiddlers[t].status=theExisting?&quot;updated&quot;:&quot;added&quot;
			if (init &amp;&amp; tags.contains(&quot;systemConfig&quot;) &amp;&amp; !tags.contains(&quot;systemConfigDisable&quot;)) {
				var ok=true;
				if (ask||!quiet) ok=confirm(config.macros.loadTiddlers.warning.format([inbound.title]))
				if (ok) { // run the plugin
					try { window.eval(inbound); tiddlers[t].status+=&quot; (plugin initialized)&quot;; }
					catch(ex) { displayMessage(config.messages.pluginError.format([exceptionText(ex)])); }
				}
			}
			count++;
		}
		store.resumeNotifications();
		if (count) {
			// refresh display
			store.setDirty(true); store.notifyAll();
			// generate a report
			if (!noreport) config.macros.loadTiddlers.report(src,tiddlers,count,quiet);
		}
		// always show final message when tiddlers were actually loaded
		if (!quiet||count) displayMessage(config.macros.loadTiddlers.loadedMsg.format([count,tiddlers.length,src.replace(/%20/g,&quot; &quot;)]));
	},
	report: function(src,tiddlers,count,quiet) {
		// format the new report content
		var newText = &quot;On &quot;+(new Date()).toLocaleString()+&quot;, &quot;;
		newText += config.options.txtUserName+&quot; loaded &quot;+count+&quot; tiddlers &quot;;
		newText += &quot;from\n[[&quot;+src+&quot;|&quot;+src+&quot;]]:\n&quot;;
		newText += &quot;&lt;&lt;&lt;\n&quot;;
		for (var t=0; t&lt;tiddlers.length; t++)
			if (tiddlers[t].status)
				newText += &quot;#[[&quot;+tiddlers[t].title+&quot;]] - &quot;+tiddlers[t].status+&quot;\n&quot;;
		newText += &quot;&lt;&lt;&lt;\n&quot;;
		// get current report (if any)
		var title=config.macros.loadTiddlers.reportTitle;
		var currText=&quot;&quot;;
		var theReport = store.getTiddler(title);
		if (theReport) currText=((theReport.text!=&quot;&quot;)?'\n----\n':&quot;&quot;)+theReport.text;
		// update the ImportedTiddlers content and show the tiddler
		store.saveTiddler(title, title, newText+currText, config.options.txtUserName, new Date(), theReport?theReport.tags:null, theReport?theReport.fields:null);
		if (!quiet) { story.displayTiddler(null,title,1,null,null,false); story.refreshTiddler(title,1,true); }
	}
}
//}}}</pre>
</div>
<div title="LoadTiddlersPluginInfo" modifier="ELSDesignStudios" created="200801090050" modified="200810271455" tags="pluginInfo ImportExportPackage">
<pre>/***
|Name|LoadTiddlersPluginInfo|
|Source|http://www.TiddlyTools.com/#LoadTiddlersPlugin|
|Documentation|http://www.TiddlyTools.com/#LoadTiddlersPluginInfo|
|Version|3.6.3|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1|
|Type|documentation|
|Requires||
|Overrides||
|Description|documentation for LoadTiddlersPlugin|
!!!!!Usage
&lt;&lt;&lt;
Syntax:
{{{&lt;&lt;loadTiddlers label:text prompt:text filter source quiet confirm force init noreport tag tag tag...&gt;&gt;}}}

Example:
{{{&lt;&lt;loadTiddlers &quot;label:load tiddlers from %0&quot; example.html confirm temporary&gt;&gt;}}}
&lt;&lt;loadTiddlers &quot;label:load tiddlers from %0&quot; example.html confirm temporary&gt;&gt;

Where:
''&quot;&quot;&quot;label:text&quot;&quot;&quot;'' and ''&quot;&quot;&quot;prompt:text&quot;&quot;&quot;''
&gt;defines link text and tooltip (prompt) that can be clicked to trigger the load tiddler processing.  If a label is NOT provided, then no link is created and the loadTiddlers function is performed whenever the containing tiddler is rendered.
''filter'' (optional) determines which tiddlers will be automatically selected for importing.  Use one of the following keywords:
&gt;''&quot;all&quot;'' retrieves ALL tiddlers from the import source document, even if they have not been changed.
&gt;''&quot;new&quot;'' retrieves only tiddlers that are found in the import source document, but do not yet exist in the destination document
&gt;''&quot;changes&quot;'' retrieves only tiddlers that exist in both documents for which the import source tiddler is newer than the existing tiddler
&gt;''&quot;updates&quot;'' retrieves both ''new'' and ''changed'' tiddlers (this is the default action when none is specified)
&gt;''&quot;&quot;&quot;&quot;tiddler:TiddlerName&quot;&quot;&quot;&quot;'' retrieves only the specific tiddler named in the parameter.
&gt;''&quot;&quot;&quot;&quot;tag:text&quot;&quot;&quot;&quot;'' retrieves only the tiddlers tagged with the indicated text.
&gt;&gt; Note: ''if an existing tiddler is tagged with 'noReload', then it will not be overwritten'', even if the inbound tiddler has been selected by the filtering process.  This allows you to make local changes to imported tiddlers while ensuring that those changes won't be lost due to automatic tiddler updates retrieved from the import source document.
''source'' (required) is the location of the imported document.  It can be either a local document path/filename in whatever format your system requires, or a remote web location (starting with &quot;http://&quot; or &quot;https://&quot;)
&gt;use the keyword ''ask'' to prompt for a source location whenever the macro is invoked
''&quot;quiet&quot;'' (optional)
&gt;supresses all status message during the import processing (e.g., &quot;opening local file...&quot;, &quot;found NN tiddlers...&quot; etc).  Note that if ANY tiddlers are actualy imported, a final information message will still be displayed (along with the ImportedTiddlers report), even when 'quiet' is specified.  This ensures that changes to your document cannot occur without any visible indication at all.
''&quot;confirm&quot;'' (optional)
&gt;adds interactive confirmation.  A browser message box (OK/Cancel) is displayed for each tiddler that will be imported, so that you can manually bypass any tiddlers that you do not want to import.
''&quot;init&quot;'' (optional)
&gt;invoke tiddlers tagged with &lt;&lt;tag systemConfig&gt;&gt; as plugins as soon as they are imported, without requiring a save-and-reload action first.  For safety, a browser message box (OK/Cancel) is displayed for each imported plugin, so that you can manually bypass any plugins that you do not want to invoke.  Note, however, that those tiddlers are still //imported// into your document and therefore will still take effect the next time you save-and-reload the document.
''&quot;force&quot;'' (optional)
&gt;import all matching tiddlers, even if unchanged
''&quot;noreport&quot;'' (optional)
&gt;suppress generation of [[ImportedTiddlers]] report
''&quot;tag tag tag...&quot;'' (optional)
&gt;any remaining parameters are used as tag values to be added to each imported tiddler (i.e., &quot;tag-on-import&quot;)
&lt;&lt;&lt;
!!!!!Configuration
&lt;&lt;&lt;
__password-protected server settings //(optional, if needed)//:__
&gt;username: &lt;&lt;option txtRemoteUsername&gt;&gt; password: &lt;&lt;option txtRemotePassword&gt;&gt;
&gt;{{{usage: &lt;&lt;option txtRemoteUsername&gt;&gt; &lt;&lt;option txtRemotePassword&gt;&gt;}}}
&gt;''note: these settings are also used by [[ExternalTiddlersPlugin]] and [[ImportTiddlersPlugin]]''
&lt;&lt;&lt;
!!!!!Revisions
&lt;&lt;&lt;
2008.10.27 [3.6.3] in doImport(), fixed Safari bug by replacing static Array.concat(...) with new Array().concat(...)
2008.08.05 [3.6.2] rewrote loadFile() to eliminate use of platform-specific fileExists() test
2008.08.03 [3.6.1] in handler(), changed variable 'prompt' to 'tip' to avoid conflict with prompt() function
2008.01.07 [3.6.0] added 'init' option to automatically invoke plugin tiddlers as soon as they are loaded (without needing save/reload)
2008.01.03 [3.5.0] in loadFile(), use lower-level doHttp() instead of loadRemoteFile() in order to support username/password access to remote server
2007.12.04 [*.*.*] update for TW2.3.0: replaced deprecated core functions, regexps, and macros
2007.06.27 [3.4.8] added missing 'fields' params to saveTiddler() call. Fixes problem where importing tiddlers would lose the custom fields.
2007.06.25 [3.4.7] add calls to store.suspendNotifications() and store.resumeNotifications() to eliminate redisplay overhead DURING import activities.
2007.05.27 [3.4.6] in handler(),  loadRemoteFile() and doImport(), added 'noreport' flag to suppress generation of ImportedTiddlers
2007.05.27 [3.4.5] in handler(),  initialize 'newTags' to [] (empty array) instead of null... fixes fatal error when loading tiddler without autotagging.
2007.04.22 [3.4.4] in readTiddlersFromHTML(), for TW2.2 and above, use importTiddlyWiki() (new core functionality) to get tiddlers from remote file content.  Also, copied updated TW21Loader.prototype.internalizeTiddler() definition from TW2.2b5 so plugin can read tiddlers from TW2.2+ even when running under TW2.1.x
2007.04.05 [3.4.3] in doImport(), changed this.readTiddlersFromHTML(html) to config.macros.loadTiddlers.readTiddlersFromHTML(html).  Fixes error caused when ImportTiddlersPlugin has NOT been installed along side this plugin.
2007.03.26 [3.4.2] renamed import() to doImport() to fix IE load-time error (&quot;identifier expected&quot;).  This may also cause a problem with FF1.5.0.x.... Apparently, &quot;import&quot; is a reserved word in some browsers...
2007.03.22 [3.4.1] code cleanup: moved all functions inside object def'n, re-wrote report function
2007.03.21 [3.4.0] split ImportTiddlersPlugin and LoadTiddlersPlugin functionality into separate plugins
|please see [[ImportTiddlersPluginInfo]] for additional revision details|
2005.07.20 [1.0.0] Initial Release
&lt;&lt;&lt;</pre>
</div>
<div title="MainMenu" modifier="TomO" created="200603020500" modified="200604280050" tags="systemTiddler">
<pre>&lt;&lt;tiddler GTDMenu&gt;&gt;
[[Help|http://www.blogjones.com/TiddlyWikiTutorial.html#EasyToEdit]][[About]]
</pre>
</div>
<div title="NavPlugin" modifier="YourName" created="200610230750" modified="200612102119" tags="systemConfig" server.type="file" server.host="lewcid.googlepages.com/tots.html#PresentationPlugin" server.page.revision="200612102119" creator="ImaiMasanobu">
<pre>// Resolves a Tiddler reference or tiddler title into a tiddler title string, or null if it doesn't exist
resolveTitle = function(t)
{
    if (t instanceof Tiddler) t = t.title;
    return store.tiddlerExists(t) ? t : null;
}

config.macros.nav = {};
config.macros.nav.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{

        if (!store.tiddlerExists(tiddler.title))
             return false;
	var e = createTiddlyElement(place,&quot;span&quot;,null,&quot;nav&quot;);
	e.setAttribute(&quot;refresh&quot;,&quot;macro&quot;);
	e.setAttribute(&quot;macroName&quot;,macroName);
	e.setAttribute(&quot;params&quot;,paramString);
        e.setAttribute(&quot;tiddler&quot;,tiddler.title)
	this.refresh(e,paramString);
}

config.macros.nav.refresh = function(place,params)
{
    var tiddler = store.getTiddler(place.getAttribute(&quot;tiddler&quot;));
    removeChildren(place);


    var params = place.getAttribute(&quot;params&quot;).parseParams(&quot;tiddlers&quot;,null,true);
    var tiddlers = getParam(params,&quot;tiddlers&quot;,undefined)||getParam(params,&quot;array&quot;,undefined);
    if (typeof tiddlers == 'string')
        tiddlers = tiddlers.readBracketedList();
    if (tiddlers == undefined)
        alert(&quot;no source tiddlers defined for navigation&quot;);
   var contents = [];
   for (var i=0;i&lt;tiddlers.length;i++)
       {
       var title = resolveTitle(tiddlers[i]);
       contents.push(title);
}
    var navIndex = contents.indexOf(tiddler.title);
    if (navIndex == -1)
        return false;
        
    if (contents[navIndex-1])
        {
        wikify(&quot;[[&lt;&lt; Previous|&quot;+contents[navIndex-1]+&quot;]]&quot;,place);
        place.lastChild.className += &quot; navPrev&quot;;
        }
    if (contents[navIndex+1])
        {
        wikify(&quot;[[Next &gt;&gt;|&quot;+contents[navIndex+1]+&quot;]]&quot;,place);
        place.lastChild.className += &quot; navNext&quot;;
        }

    var theTable = createTiddlyElement(place,&quot;table&quot;,null,&quot;nav&quot;);
    var theBody = createTiddlyElement(theTable,&quot;tbody&quot;);
    var theRow = createTiddlyElement(theBody,&quot;tr&quot;);
    for (var i=0; i&lt;contents.length; i++)
        {
        var box = createTiddlyElement(theRow,&quot;td&quot;,null,&quot;navlinkcell&quot;,&quot; &quot;);
        box.onclick = onClickTiddlerLink;
	    box.setAttribute(&quot;tiddlyLink&quot;,contents[i]);
        box.title = (contents[i]);
        if (contents[i] ==tiddler.title)
           box.className += &quot; activenav&quot;;
        }
}</pre>
</div>
<div title="NestedSlidersPlugin" modifier="ELSDesignStudios" created="200512161202" modified="200806072045" tags="DiscoveryPackage TaskPackage TidIDEPackage systemConfig">
<pre>/***
|Name|NestedSlidersPlugin|
|Source|http://www.TiddlyTools.com/#NestedSlidersPlugin|
|Documentation|http://www.TiddlyTools.com/#NestedSlidersPluginInfo|
|Version|2.4.5|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1|
|Type|plugin|
|Requires||
|Overrides||
|Options|##Configuration|
|Description|show content in nest-able sliding/floating panels, without creating separate tiddlers for each panel's content|
!!!!!Documentation
&gt;see [[NestedSlidersPluginInfo]]
!!!!!Configuration
&lt;&lt;&lt;
&lt;&lt;option chkFloatingSlidersAnimate&gt;&gt; allow floating sliders to animate when opening/closing
&gt;Note: This setting can cause 'clipping' problems in some versions of InternetExplorer.
&gt;In addition, for floating slider animation to occur you must also allow animation in general (see [[AdvancedOptions]]).
&lt;&lt;&lt;
!!!!!Revisions
&lt;&lt;&lt;
2008.06.07 - 2.4.5 in 'onmouseover' handler for 'open on hover' slider buttons,&lt;br&gt;use call() method when invoking document.onclick function (avoids error in IE)
|please see [[NestedSlidersPluginInfo]] for additional revision details|
2005.11.03 - 1.0.0 initial public release.  Thanks to RodneyGomes, GeoffSlocock, and PaulPetterson for suggestions and experiments.
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.nestedSliders = {major: 2, minor: 4, revision: 5, date: new Date(2008,6,7)};

// options for deferred rendering of sliders that are not initially displayed
if (config.options.chkFloatingSlidersAnimate===undefined)
	config.options.chkFloatingSlidersAnimate=false; // avoid clipping problems in IE

// default styles for 'floating' class
setStylesheet(&quot;.floatingPanel { position:absolute; z-index:10; padding:0.5em; margin:0em; \
	background-color:#eee; color:#000; border:1px solid #000; text-align:left; }&quot;,&quot;floatingPanelStylesheet&quot;);

config.formatters.push( {
	name: &quot;nestedSliders&quot;,
	match: &quot;\\n?\\+{3}&quot;,
	terminator: &quot;\\s*\\={3}\\n?&quot;,
	lookahead: &quot;\\n?\\+{3}(\\+)?(\\([^\\)]*\\))?(\\!*)?(\\^(?:[^\\^\\*\\@\\[\\&gt;]*\\^)?)?(\\*)?(\\@)?(?:\\{\\{([\\w]+[\\s\\w]*)\\{)?(\\[[^\\]]*\\])?(\\[[^\\]]*\\])?(?:\\}{3})?(\\#[^:]*\\:)?(\\&gt;)?(\\.\\.\\.)?\\s*&quot;,
	handler: function(w)
		{
			lookaheadRegExp = new RegExp(this.lookahead,&quot;mg&quot;);
			lookaheadRegExp.lastIndex = w.matchStart;
			var lookaheadMatch = lookaheadRegExp.exec(w.source)
			if(lookaheadMatch &amp;&amp; lookaheadMatch.index == w.matchStart)
			{
				var defopen=lookaheadMatch[1];
				var cookiename=lookaheadMatch[2];
				var header=lookaheadMatch[3];
				var panelwidth=lookaheadMatch[4];
				var transient=lookaheadMatch[5];
				var hover=lookaheadMatch[6];
				var buttonClass=lookaheadMatch[7];
				var label=lookaheadMatch[8];
				var openlabel=lookaheadMatch[9];
				var panelID=lookaheadMatch[10];
				var blockquote=lookaheadMatch[11];
				var deferred=lookaheadMatch[12];

				// location for rendering button and panel
				var place=w.output;

				// default to closed, no cookie, no accesskey, no alternate text/tip
				var show=&quot;none&quot;; var cookie=&quot;&quot;; var key=&quot;&quot;;
				var closedtext=&quot;&gt;&quot;; var closedtip=&quot;&quot;;
				var openedtext=&quot;&lt;&quot;; var openedtip=&quot;&quot;;

				// extra &quot;+&quot;, default to open
				if (defopen) show=&quot;block&quot;;

				// cookie, use saved open/closed state
				if (cookiename) {
					cookie=cookiename.trim().slice(1,-1);
					cookie=&quot;chkSlider&quot;+cookie;
					if (config.options[cookie]==undefined)
						{ config.options[cookie] = (show==&quot;block&quot;) }
					show=config.options[cookie]?&quot;block&quot;:&quot;none&quot;;
				}

				// parse label/tooltip/accesskey: [label=X|tooltip]
				if (label) {
					var parts=label.trim().slice(1,-1).split(&quot;|&quot;);
					closedtext=parts.shift();
					if (closedtext.substr(closedtext.length-2,1)==&quot;=&quot;)	
						{ key=closedtext.substr(closedtext.length-1,1); closedtext=closedtext.slice(0,-2); }
					openedtext=closedtext;
					if (parts.length) closedtip=openedtip=parts.join(&quot;|&quot;);
					else { closedtip=&quot;show &quot;+closedtext; openedtip=&quot;hide &quot;+closedtext; }
				}

				// parse alternate label/tooltip: [label|tooltip]
				if (openlabel) {
					var parts=openlabel.trim().slice(1,-1).split(&quot;|&quot;);
					openedtext=parts.shift();
					if (parts.length) openedtip=parts.join(&quot;|&quot;);
					else openedtip=&quot;hide &quot;+openedtext;
				}

				var title=show=='block'?openedtext:closedtext;
				var tooltip=show=='block'?openedtip:closedtip;

				// create the button
				if (header) { // use &quot;Hn&quot; header format instead of button/link
					var lvl=(header.length&gt;5)?5:header.length;
					var btn = createTiddlyElement(createTiddlyElement(place,&quot;h&quot;+lvl,null,null,null),&quot;a&quot;,null,buttonClass,title);
					btn.onclick=onClickNestedSlider;
					btn.setAttribute(&quot;href&quot;,&quot;javascript:;&quot;);
					btn.setAttribute(&quot;title&quot;,tooltip);
				}
				else
					var btn = createTiddlyButton(place,title,tooltip,onClickNestedSlider,buttonClass);
				btn.innerHTML=title; // enables use of HTML entities in label

				// set extra button attributes
				btn.setAttribute(&quot;closedtext&quot;,closedtext);
				btn.setAttribute(&quot;closedtip&quot;,closedtip);
				btn.setAttribute(&quot;openedtext&quot;,openedtext);
				btn.setAttribute(&quot;openedtip&quot;,openedtip);
				btn.sliderCookie = cookie; // save the cookiename (if any) in the button object
				btn.defOpen=defopen!=null; // save default open/closed state (boolean)
				btn.keyparam=key; // save the access key letter (&quot;&quot; if none)
				if (key.length) {
					btn.setAttribute(&quot;accessKey&quot;,key); // init access key
					btn.onfocus=function(){this.setAttribute(&quot;accessKey&quot;,this.keyparam);}; // **reclaim** access key on focus
				}
				btn.setAttribute(&quot;hover&quot;,hover?&quot;true&quot;:&quot;false&quot;);
				btn.onmouseover=function(ev) {
					// optional 'open on hover' handling
					if (this.getAttribute(&quot;hover&quot;)==&quot;true&quot; &amp;&amp; this.sliderPanel.style.display=='none') {
						document.onclick.call(document,ev); // close transients
						onClickNestedSlider(ev); // open this slider
					}
					// mouseover on button aligns floater position with button
					if (window.adjustSliderPos) window.adjustSliderPos(this.parentNode,this,this.sliderPanel);
				}

				// create slider panel
				var panelClass=panelwidth?&quot;floatingPanel&quot;:&quot;sliderPanel&quot;;
				if (panelID) panelID=panelID.slice(1,-1); // trim off delimiters
				var panel=createTiddlyElement(place,&quot;div&quot;,panelID,panelClass,null);
				panel.button = btn; // so the slider panel know which button it belongs to
				btn.sliderPanel=panel; // so the button knows which slider panel it belongs to
				panel.defaultPanelWidth=(panelwidth &amp;&amp; panelwidth.length&gt;2)?panelwidth.slice(1,-1):&quot;&quot;;
				panel.setAttribute(&quot;transient&quot;,transient==&quot;*&quot;?&quot;true&quot;:&quot;false&quot;);
				panel.style.display = show;
				panel.style.width=panel.defaultPanelWidth;
				panel.onmouseover=function(event) // mouseover on panel aligns floater position with button
					{ if (window.adjustSliderPos) window.adjustSliderPos(this.parentNode,this.button,this); }

				// render slider (or defer until shown) 
				w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
				if ((show==&quot;block&quot;)||!deferred) {
					// render now if panel is supposed to be shown or NOT deferred rendering
					w.subWikify(blockquote?createTiddlyElement(panel,&quot;blockquote&quot;):panel,this.terminator);
					// align floater position with button
					if (window.adjustSliderPos) window.adjustSliderPos(place,btn,panel);
				}
				else {
					var src = w.source.substr(w.nextMatch);
					var endpos=findMatchingDelimiter(src,&quot;+++&quot;,&quot;===&quot;);
					panel.setAttribute(&quot;raw&quot;,src.substr(0,endpos));
					panel.setAttribute(&quot;blockquote&quot;,blockquote?&quot;true&quot;:&quot;false&quot;);
					panel.setAttribute(&quot;rendered&quot;,&quot;false&quot;);
					w.nextMatch += endpos+3;
					if (w.source.substr(w.nextMatch,1)==&quot;\n&quot;) w.nextMatch++;
				}
			}
		}
	}
)

function findMatchingDelimiter(src,starttext,endtext) {
	var startpos = 0;
	var endpos = src.indexOf(endtext);
	// check for nested delimiters
	while (src.substring(startpos,endpos-1).indexOf(starttext)!=-1) {
		// count number of nested 'starts'
		var startcount=0;
		var temp = src.substring(startpos,endpos-1);
		var pos=temp.indexOf(starttext);
		while (pos!=-1)  { startcount++; pos=temp.indexOf(starttext,pos+starttext.length); }
		// set up to check for additional 'starts' after adjusting endpos
		startpos=endpos+endtext.length;
		// find endpos for corresponding number of matching 'ends'
		while (startcount &amp;&amp; endpos!=-1) {
			endpos = src.indexOf(endtext,endpos+endtext.length);
			startcount--;
		}
	}
	return (endpos==-1)?src.length:endpos;
}
//}}}
//{{{
window.onClickNestedSlider=function(e)
{
	if (!e) var e = window.event;
	var theTarget = resolveTarget(e);
	while (theTarget &amp;&amp; theTarget.sliderPanel==undefined) theTarget=theTarget.parentNode;
	if (!theTarget) return false;
	var theSlider = theTarget.sliderPanel;
	var isOpen = theSlider.style.display!=&quot;none&quot;;

	// toggle label
	theTarget.innerHTML=isOpen?theTarget.getAttribute(&quot;closedText&quot;):theTarget.getAttribute(&quot;openedText&quot;);
	// toggle tooltip
	theTarget.setAttribute(&quot;title&quot;,isOpen?theTarget.getAttribute(&quot;closedTip&quot;):theTarget.getAttribute(&quot;openedTip&quot;));

	// deferred rendering (if needed)
	if (theSlider.getAttribute(&quot;rendered&quot;)==&quot;false&quot;) {
		var place=theSlider;
		if (theSlider.getAttribute(&quot;blockquote&quot;)==&quot;true&quot;)
			place=createTiddlyElement(place,&quot;blockquote&quot;);
		wikify(theSlider.getAttribute(&quot;raw&quot;),place);
		theSlider.setAttribute(&quot;rendered&quot;,&quot;true&quot;);
	}
	// show/hide the slider
	if(config.options.chkAnimate &amp;&amp; (!hasClass(theSlider,'floatingPanel') || config.options.chkFloatingSlidersAnimate))
		anim.startAnimating(new Slider(theSlider,!isOpen,e.shiftKey || e.altKey,&quot;none&quot;));
	else
		theSlider.style.display = isOpen ? &quot;none&quot; : &quot;block&quot;;
	// reset to default width (might have been changed via plugin code)
	theSlider.style.width=theSlider.defaultPanelWidth;
	// align floater panel position with target button
	if (!isOpen &amp;&amp; window.adjustSliderPos) window.adjustSliderPos(theSlider.parentNode,theTarget,theSlider);
	// if showing panel, set focus to first 'focus-able' element in panel
	if (theSlider.style.display!=&quot;none&quot;) {
		var ctrls=theSlider.getElementsByTagName(&quot;*&quot;);
		for (var c=0; c&lt;ctrls.length; c++) {
			var t=ctrls[c].tagName.toLowerCase();
			if ((t==&quot;input&quot; &amp;&amp; ctrls[c].type!=&quot;hidden&quot;) || t==&quot;textarea&quot; || t==&quot;select&quot;)
				{ ctrls[c].focus(); break; }
		}
	}
	var cookie=theTarget.sliderCookie;
	if (cookie &amp;&amp; cookie.length) {
		config.options[cookie]=!isOpen;
		if (config.options[cookie]!=theTarget.defOpen)
			saveOptionCookie(cookie);
		else { // remove cookie if slider is in default display state
			var ex=new Date(); ex.setTime(ex.getTime()-1000);
			document.cookie = cookie+&quot;=novalue; path=/; expires=&quot;+ex.toGMTString();
		}
	}

	// prevent SHIFT-CLICK from being processed by browser (opens blank window... yuck!)
	// prevent clicks *within* a slider button from being processed by browser
	// but allow plain click to bubble up to page background (to close transients, if any)
	if (e.shiftKey || theTarget!=resolveTarget(e))
		{ e.cancelBubble=true; if (e.stopPropagation) e.stopPropagation(); }
	Popup.remove(); // close open popup (if any)
	return false;
}
//}}}
//{{{
// click in document background closes transient panels 
document.nestedSliders_savedOnClick=document.onclick;
document.onclick=function(ev) { if (!ev) var ev=window.event; var target=resolveTarget(ev);

	if (document.nestedSliders_savedOnClick)
		var retval=document.nestedSliders_savedOnClick.apply(this,arguments);
	// if click was inside a popup... leave transient panels alone
	var p=target; while (p) if (hasClass(p,&quot;popup&quot;)) break; else p=p.parentNode;
	if (p) return retval;
	// if click was inside transient panel (or something contained by a transient panel), leave it alone
	var p=target; while (p) {
		if ((hasClass(p,&quot;floatingPanel&quot;)||hasClass(p,&quot;sliderPanel&quot;))&amp;&amp;p.getAttribute(&quot;transient&quot;)==&quot;true&quot;) break;
		p=p.parentNode;
	}
	if (p) return retval;
	// otherwise, find and close all transient panels...
	var all=document.all?document.all:document.getElementsByTagName(&quot;DIV&quot;);
	for (var i=0; i&lt;all.length; i++) {
		 // if it is not a transient panel, or the click was on the button that opened this panel, don't close it.
		if (all[i].getAttribute(&quot;transient&quot;)!=&quot;true&quot; || all[i].button==target) continue;
		// otherwise, if the panel is currently visible, close it by clicking it's button
		if (all[i].style.display!=&quot;none&quot;) window.onClickNestedSlider({target:all[i].button}) 
	}
	return retval;
};
//}}}
//{{{
// adjust floating panel position based on button position
if (window.adjustSliderPos==undefined) window.adjustSliderPos=function(place,btn,panel) {
	if (hasClass(panel,&quot;floatingPanel&quot;)) {
		var rightEdge=document.body.offsetWidth-1;
		var panelWidth=panel.offsetWidth;
		var left=0;
		var top=btn.offsetHeight; 
		if (place.style.position==&quot;relative&quot; &amp;&amp; findPosX(btn)+panelWidth&gt;rightEdge) {
			left-=findPosX(btn)+panelWidth-rightEdge; // shift panel relative to button
			if (findPosX(btn)+left&lt;0) left=-findPosX(btn); // stay within left edge
		}
		if (place.style.position!=&quot;relative&quot;) {
			var left=findPosX(btn);
			var top=findPosY(btn)+btn.offsetHeight;
			var p=place; while (p &amp;&amp; !hasClass(p,'floatingPanel')) p=p.parentNode;
			if (p) { left-=findPosX(p); top-=findPosY(p); }
			if (left+panelWidth&gt;rightEdge) left=rightEdge-panelWidth;
			if (left&lt;0) left=0;
		}
		panel.style.left=left+&quot;px&quot;; panel.style.top=top+&quot;px&quot;;
	}
}
//}}}
//{{{
// TW2.1 and earlier:
// hijack Slider stop handler so overflow is visible after animation has completed
Slider.prototype.coreStop = Slider.prototype.stop;
Slider.prototype.stop = function()
	{ this.coreStop.apply(this,arguments); this.element.style.overflow = &quot;visible&quot;; }

// TW2.2+
// hijack Morpher stop handler so sliderPanel/floatingPanel overflow is visible after animation has completed
if (version.major+.1*version.minor+.01*version.revision&gt;=2.2) {
	Morpher.prototype.coreStop = Morpher.prototype.stop;
	Morpher.prototype.stop = function() {
		this.coreStop.apply(this,arguments);
		var e=this.element;
		if (hasClass(e,&quot;sliderPanel&quot;)||hasClass(e,&quot;floatingPanel&quot;)) {
			// adjust panel overflow and position after animation
			e.style.overflow = &quot;visible&quot;;
			if (window.adjustSliderPos) window.adjustSliderPos(e.parentNode,e.button,e);
		}
	};
}
//}}}</pre>
</div>
<div title="NewActionTemplate" modifier="TomO" created="200603020500" modified="200611071705" tags="gtd template">
<pre></pre>
</div>
<div title="NewContextTemplate" modifier="TomO" created="200603020500" modified="200610220103" tags="gtd template">
<pre>&lt;&lt;gtdActionList&gt;&gt;</pre>
</div>
<div title="NewProjectTemplate" modifier="TomO" created="200603020500" modified="200610220104" tags="gtd template">
<pre>The outcome for this project is </pre>
</div>
<div title="Official d-cubed feed" modifier="TomO" created="200806290037" modified="200810252142" tags="systemServer gtd excludeLists" changecount="1">
<pre>|''URL:''|http://dcubed.ca/d3-update-13.html|
|''Description:''|Official d-cubed feed|
|''Author:''|Tom Otvos|</pre>
</div>
<div title="PageTemplate" modifier="TomO" created="200603020500" modified="200604280055" tags="template">
<pre>&lt;div class='header' macro='gradient vert #000 #464646'&gt;
&lt;div class='headerShadow'&gt;
&lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
&lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class='headerForeground'&gt;
&lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
&lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id='mainMenu' refresh='content' tiddler='MainMenu' force='true'&gt;&lt;/div&gt;
&lt;div id='sidebar'&gt;
&lt;div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'&gt;&lt;/div&gt;
&lt;div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id='displayArea'&gt;
&lt;div id='messageArea'&gt;&lt;/div&gt;
&lt;div id='tiddlerDisplay'&gt;&lt;/div&gt;
&lt;/div&gt;</pre>
</div>
<div title="PresentationPlugin" created="200609240907" modified="200612102048" tags="systemConfig" server.type="file" server.host="lewcid.googlepages.com/tots.html#PresentationPlugin" server.page.revision="200612102048" creator="ImaiMasanobu">
<pre>//{{{
TiddlyWiki.prototype.removeNotification = function(title,fn) {
	for (var i=0;i&lt;this.namedNotifications.length;i++)
		if((this.namedNotifications[i].name == title) &amp;&amp; (this.namedNotifications[i].notify == fn))
			this.namedNotifications.splice(i,1);
}
      
//checks to see if a tiddler exists in store or as a shadow.
TiddlyWiki.prototype.isTiddler= function (title)
        {return store.tiddlerExists(title) || store.isShadowTiddler(title)}

// Refresh all tiddlers in the Story
Story.prototype.lewcidrefreshAllTiddlers = function()
{
    var place = document.getElementById(this.container);
    var e = place.firstChild;
    if(!e) return;
    this.refreshTiddler(e.getAttribute(&quot;tiddler&quot;),null,true);
    while((e = e.nextSibling) != null)
        this.refreshTiddler(e.getAttribute(&quot;tiddler&quot;),null,true);
}

config.presentationPlugin ={
};

config.presentationPlugin.defaults = [
	{name: &quot;StyleSheet&quot;, notify: refreshStyles},
	{name: &quot;PageTemplate&quot;, notify: refreshPageTemplate}
	];

window.presentationMode='';

function applyPresentationMode (oldMode,Mode)
{
    presentationMode = Mode;
    var defaults = config.presentationPlugin.defaults;
    var oldStyleElement = document.getElementById(oldMode+&quot;StyleSheet&quot;);
    if (oldStyleElement)
        {
        oldStyleElement.parentNode.removeChild(oldStyleElement);
        }
    for (var i=0; i&lt;defaults.length; i++)
      {
        var def = defaults[i][&quot;name&quot;];
        var newMode = store.isTiddler(Mode + def)? Mode + def : def;
        store.removeNotification(oldMode + def, defaults[i][&quot;notify&quot;]);
        store.addNotification(newMode,defaults[i][&quot;notify&quot;]);
        store.notify(newMode); //just one do blanket notify instead?
      }
    story.lewcidrefreshAllTiddlers();
}

config.macros.author={};
config.macros.author.handler= function (place,macroName,params,wikifier,paramString,tiddler) {
	var e = createTiddlyElement(place,&quot;div&quot;);
	e.setAttribute(&quot;refresh&quot;,&quot;macro&quot;);
	e.setAttribute(&quot;macroName&quot;,&quot;author&quot;);
	e.setAttribute(&quot;params&quot;,paramString);
	this.refresh(e,paramString);
}

config.macros.author.refresh = function(place,params){
    if (window.lewcideditmode== false)
        return false;
    removeChildren(place);
    var oldMode = window.presentationMode;
    var newMode = (oldMode == &quot;Author&quot;)?&quot;&quot;:&quot;Author&quot;;
    var label = (oldMode == &quot;Author&quot;)? &quot;Presentation Mode&quot;:&quot;Author Mode&quot;;
    var tooltip = label;
	createTiddlyButton(place,label,tooltip,function() {
		applyPresentationMode(oldMode,newMode);
	});
};

Story.prototype.chooseTemplateForTiddler_old_presentation = Story.prototype.chooseTemplateForTiddler;

Story.prototype.chooseTemplateForTiddler = function(title,template)
{
    if (!template)
        template = DEFAULT_VIEW_TEMPLATE;
    var mode = presentationMode;
    if (template == DEFAULT_VIEW_TEMPLATE)
        {
        if (store.isTiddler(mode+&quot;ViewTemplate&quot;))
            return mode+&quot;ViewTemplate&quot;;
        }
    else if (template == DEFAULT_EDIT_TEMPLATE)
        {
        if (store.isTiddler(mode+&quot;EditTemplate&quot;))
            return mode+&quot;EditTemplate&quot;;
        }
    return this.chooseTemplateForTiddler_old_presentation(title,template);
}

window.lewcideditmode = false;
config.paramifiers.author = {
	onstart: function(v) {
                 if (v!=&quot;true&quot;)
                     return false;
                applyPresentationMode(&quot;&quot;,&quot;Author&quot;);
                window.lewcideditmode =  true;
                refreshDisplay();
	            }
};
//}}}</pre>
</div>
<div title="Project Review" modifier="TomO" created="200603020500" modified="200611300306" tags="gtd review">
<pre>! Active projects with remaining actions
&lt;&lt;gtdActionList *&gt;&gt;
! Projects with no open actions
&lt;&lt;list tagged &quot;project done -someday&quot;&gt;&gt;</pre>
</div>
<div title="ProjectList" modifier="TomO" created="200611071454" modified="200611101346" tags="gtd">
<pre>*+++(gtdImportantProjectsSliderState)[Important:] &lt;&lt;list tagged &quot;project important -someday&quot; all&gt;&gt;===
+++(gtdOtherProjectsSliderState)[Other:] &lt;&lt;list tagged &quot;project -syntora -important -someday&quot; all&gt;&gt;===</pre>
</div>
<div title="Reference" modifier="TomO" created="200603020500" modified="200603212237" tags="gtd">
<pre>&lt;&lt;list tagged txtGTDReferenceContext any&gt;&gt;</pre>
</div>
<div title="ReminderMacros" modifier="TomO" created="200603020500" modified="200612152046" tags="gtd systemConfig">
<pre>/***
|''Name:''|ReminderPlugin|
|''Version:''|2.3.8.3 (Nov 28, 2006)|
|''Source:''|http://www.geocities.com/allredfaq/reminderMacros.html|
|''Author:''|Jeremy Sheeley(pop1280 [at] excite [dot] com)|
|''Modified by:''|Tom Otvos|
|''Licence:''|[[BSD open source license]]|
|''Macros:''|reminder, showreminders, displayTiddlersWithReminders, newReminder|
|''TiddlyWiki:''|2.0+|
|''Browser:''|Firefox 1.0.4+; InternetExplorer 6.0|

!Description
This plugin provides macros for tagging a date with a reminder.  Use the {{{reminder}}} macro to do this.  The {{{showReminders}}} and {{{displayTiddlersWithReminder}}} macros automatically search through all available tiddlers looking for upcoming reminders.

''This version contains a fix by Tom Otvos that modifies tag filtering when tiddlers contain no tags. In this version, if you are filtering showReminders by tag, and a tiddler has no tags, the filter will //not// match the tiddler. There are also a number of small UI-related changes to make the reminder interface a bit nicer. Do not copy this into your own TW documents unless you accept these changes.''

!Installation
* Create a new tiddler in your tiddlywiki titled ReminderPlugin and give it the {{{systemConfig}}} tag.  The tag is important because it tells TW that this is executable code.
* Double click this tiddler, and copy all the text from the tiddler's body.
* Paste the text into the body of the new tiddler in your TW.
* Save and reload your TW.
* You can copy some examples into your TW as well.  See [[Simple examples]], [[Holidays]], [[showReminders]] and [[Personal Reminders]]

!Syntax:
|&gt;|See [[ReminderSyntax]] and [[showRemindersSyntax]]|

!Revision history
* v2.3.8 (Mar 9, 2006)
**Bug fix: A global variable had snuck in, which was killing FF 1.5.0.1
**Feature: You can now use TIDDLER and TIDDLERNAME in a regular reminder format
* v2.3.6 (Mar 1, 2006)
**Bug fix: Reminders for today weren't being matched sometimes.
**Feature:  Solidified integration with DatePlugin and CalendarPlugin
**Feature:  Recurring reminders will now return multiple hits in showReminders and the calendar.
**Feature:  Added TIDDLERNAME to the replacements for showReminders format, for plugins that need the title without brackets.
* v2.3.5 (Feb 8, 2006)
**Bug fix: Sped up reminders lots.  Added a caching mechanism for reminders that have already been matched.
* v2.3.4 (Feb 7, 2006)
**Bug fix: Cleaned up code to hopefully prevent the Firefox 1.5.0.1 crash that was causing lots of plugins 
to crash Firefox.  Thanks to http://www.jslint.com
* v2.3.3 (Feb 2, 2006)
**Feature: newReminder now has drop down lists instead of text boxes.
**Bug fix:  A trailing space in a title would trigger an infinite loop.
**Bug fix:  using tag:&quot;birthday !reminder&quot; would filter differently than tag:&quot;!reminder birthday&quot;
* v2.3.2 (Jan 21, 2006)
**Feature: newReminder macro, which will let you easily add a reminder to a tiddler. Thanks to Eric Shulman (http://www.elsdesign.com) for the code to do this.
** Bug fix: offsetday was not working sometimes
** Bug fix: when upgrading to 2.0, I included a bit to exclude tiddlers tagged with excludeSearch.  I've reverted back to searching through all tiddlers
* v2.3.1 (Jan 7, 2006)
**Feature: 2.0 compatibility
**Feature AlanH sent some code to make sure that showReminders prints a message if no reminders are found.
* v2.3.0 (Jan 3, 2006)
** Bug Fix:  Using &quot;Last Sunday (-0)&quot; as a offsetdayofweek wasn't working.
** Bug Fix:  Daylight Savings time broke offset based reminders (for example year:2005 month:8 day:23 recurdays:7 would match Monday instead of Tuesday during DST.

!Code
***/
//{{{

//============================================================================
//============================================================================
//           ReminderPlugin
//============================================================================
//============================================================================

version.extensions.ReminderPlugin = {major: 2, minor: 3, revision: 8, date: new Date(2006,3,9), source: &quot;http://www.geocities.com/allredfaq/reminderMacros.html&quot;};

//============================================================================
// Configuration
// Modify this section to change the defaults for 
// leadtime and display strings
//============================================================================

config.macros.reminders = {};
config.macros[&quot;reminder&quot;] = {};
config.macros[&quot;newReminder&quot;] = {};
config.macros[&quot;showReminders&quot;] = {};
config.macros[&quot;displayTiddlersWithReminders&quot;] = {};

config.macros.reminders[&quot;defaultLeadTime&quot;] = [0,365];
config.macros.reminders[&quot;defaultReminderMessage&quot;] = &quot;DIFF: TITLE on DATE ANNIVERSARY&quot;;
config.macros.reminders[&quot;defaultShowReminderMessage&quot;] = &quot;DIFF: TITLE on DATE ANNIVERSARY -- TIDDLER&quot;;
config.macros.reminders[&quot;defaultAnniversaryMessage&quot;] = &quot;(DIFF)&quot;;
config.macros.reminders[&quot;untitledReminder&quot;] = &quot;Untitled Reminder&quot;;
config.macros.reminders[&quot;noReminderFound&quot;] = &quot;Couldn't find a match for TITLE in the next LEADTIMEUPPER days.&quot;
config.macros.reminders[&quot;todayString&quot;] = &quot;Today&quot;;
config.macros.reminders[&quot;tomorrowString&quot;] = &quot;Tomorrow&quot;;
config.macros.reminders[&quot;ndaysString&quot;] = &quot;DIFF days&quot;;
config.macros.reminders[&quot;emtpyShowRemindersString&quot;] = &quot;There are no upcoming events&quot;;


//============================================================================
//  Code
// You should not need to edit anything 
// below this.  Make sure to edit this tiddler and copy 
// the code from the text box, to make sure that 
// tiddler rendering doesn't interfere with the copy 
// and paste.
//============================================================================

// This line is to preserve 1.2 compatibility
// if (!story) var story=window; 
//this object will hold the cache of reminders, so that we don't
//recompute the same reminder over again.
var reminderCache = {};

config.macros.showReminders.handler = function showReminders(place,macroName,params)
{
   var now = new Date().getMidnight();
   var paramHash = {};
   var leadtime = [0,14];
   paramHash = getParamsForReminder(params);
   var bProvidedDate = (paramHash[&quot;year&quot;] != null) || 
			(paramHash[&quot;month&quot;] != null) || 
			(paramHash[&quot;day&quot;] != null) || 
			(paramHash[&quot;dayofweek&quot;] != null);
   if (paramHash[&quot;leadtime&quot;] != null)
   {
      leadtime = paramHash[&quot;leadtime&quot;];
      if (bProvidedDate)
      {
         //If they've entered a day, we need to make 
         //sure to find it.  We'll reset the 
         //leadtime a few lines down.
         paramHash[&quot;leadtime&quot;] = [-10000, 10000];
      }
   }
   var matchedDate = now;
   if (bProvidedDate)
   {
      var leadTimeLowerBound = new Date().getMidnight().addDays(paramHash[&quot;leadtime&quot;][0]);
      var leadTimeUpperBound = new Date().getMidnight().addDays(paramHash[&quot;leadtime&quot;][1]);
      matchedDate = findDateForReminder(paramHash, new Date().getMidnight(), leadTimeLowerBound, leadTimeUpperBound); 
   }

   var arr = findTiddlersWithReminders(matchedDate, leadtime, paramHash[&quot;tag&quot;], paramHash[&quot;limit&quot;]);
   var elem = createTiddlyElement(place,&quot;span&quot;,null,null, null);
   var mess = &quot;&quot;;
   if (arr.length == 0)
   {
      mess += config.macros.reminders.emtpyShowRemindersString; 
   }
   for (var j = 0; j &lt; arr.length; j++)
   {
      if (paramHash[&quot;format&quot;] != null)
      {
         arr[j][&quot;params&quot;][&quot;format&quot;] = paramHash[&quot;format&quot;];
      }
      else
      {
         arr[j][&quot;params&quot;][&quot;format&quot;] = config.macros.reminders[&quot;defaultShowReminderMessage&quot;];
      }
      mess += getReminderMessageForDisplay(arr[j][&quot;diff&quot;], arr[j][&quot;params&quot;], arr[j][&quot;matchedDate&quot;], arr[j][&quot;tiddler&quot;]);
      mess += &quot;\n&quot;;
   }
   wikify(mess, elem, null, null);
};


config.macros.displayTiddlersWithReminders.handler = function displayTiddlersWithReminders(place,macroName,params)
{
   var now = new Date().getMidnight();
   var paramHash = {};
   var leadtime = [0,14];
   paramHash = getParamsForReminder(params);
   var bProvidedDate = (paramHash[&quot;year&quot;] != null) || 
			(paramHash[&quot;month&quot;] != null) || 
			(paramHash[&quot;day&quot;] != null) || 
			(paramHash[&quot;dayofweek&quot;] != null);
   if (paramHash[&quot;leadtime&quot;] != null)
   {
      leadtime = paramHash[&quot;leadtime&quot;];
      if (bProvidedDate)
      {
         //If they've entered a day, we need to make 
         //sure to find it.  We'll reset the leadtime 
         //a few lines down.
         paramHash[&quot;leadtime&quot;] = [-10000,10000];
      }
   }
   var matchedDate = now;
   if (bProvidedDate)
   {
      var leadTimeLowerBound = new Date().getMidnight().addDays(paramHash[&quot;leadtime&quot;][0]);
      var leadTimeUpperBound = new Date().getMidnight().addDays(paramHash[&quot;leadtime&quot;][1]);
      matchedDate = findDateForReminder(paramHash, new Date().getMidnight(), leadTimeLowerBound, leadTimeUpperBound); 
   }
   var arr = findTiddlersWithReminders(matchedDate, leadtime, paramHash[&quot;tag&quot;], paramHash[&quot;limit&quot;]);
   for (var j = 0; j &lt; arr.length; j++)
   {
      displayTiddler(null, arr[j][&quot;tiddler&quot;], 0, null, false, false, false);
   }
};

config.macros.reminder.handler = function reminder(place,macroName,params)
{
   var dateHash = getParamsForReminder(params);
   if (dateHash[&quot;hidden&quot;] != null)
   {
      return;
   }
   var leadTime = dateHash[&quot;leadtime&quot;];
   if (leadTime == null)
   {
      leadTime = config.macros.reminders[&quot;defaultLeadTime&quot;]; 
   }
   var leadTimeLowerBound = new Date().getMidnight().addDays(leadTime[0]);
   var leadTimeUpperBound = new Date().getMidnight().addDays(leadTime[1]);
   var matchedDate = findDateForReminder(dateHash, new Date().getMidnight(), leadTimeLowerBound, leadTimeUpperBound);
   if (!window.story) 
   {
      window.story=window; 
   }
   if (!store.getTiddler) 
   {
      store.getTiddler=function(title) {return this.tiddlers[title];};
   }
   var title = window.story.findContainingTiddler(place).id.substr(7);
   if (matchedDate != null)
   {
      var diff = matchedDate.getDifferenceInDays(new Date().getMidnight());
      var elem = createTiddlyElement(place,&quot;span&quot;,null,null, null);
      var mess = getReminderMessageForDisplay(diff, dateHash, matchedDate, title);
      wikify(mess, elem, null, null);
   }
   else
   {
      createTiddlyElement(place,&quot;span&quot;,null,null, config.macros.reminders[&quot;noReminderFound&quot;].replace(&quot;TITLE&quot;, dateHash[&quot;title&quot;]).replace(&quot;LEADTIMEUPPER&quot;, leadTime[1]).replace(&quot;LEADTIMELOWER&quot;, leadTime[0]).replace(&quot;TIDDLERNAME&quot;, title).replace(&quot;TIDDLER&quot;, &quot;[[&quot; + title + &quot;]]&quot;) );
   }
};

config.macros.newReminder.handler = function newReminder(place,macroName,params)
{
	// tomo - cleaned up the form layout a bit
	var today=new Date().getMidnight();
	var formstring = '&lt;html&gt;&lt;br/&gt;&lt;form&gt;';
	
	formstring += 'Year: &lt;select name=&quot;year&quot;&gt;&lt;option value=&quot;&quot;&gt;Every year&lt;/option&gt;';
	for (var i = 0; i &lt; 5; i++)
	{
		formstring += '&lt;option' + ((i == 0) ? ' selected' : '') + ' value=&quot;' + (today.getFullYear() +i) + '&quot;&gt;' + (today.getFullYear() + i) + '&lt;/option&gt;';
	}
	formstring += '&lt;/select&gt;&amp;nbsp;&amp;nbsp;Month: &lt;select name=&quot;month&quot;&gt;&lt;option value=&quot;&quot;&gt;Every month&lt;/option&gt;';
	for (i = 0; i &lt; 12; i++)
	{
		formstring += '&lt;option' + ((i == today.getMonth()) ? ' selected' : '') + ' value=&quot;' + (i+1) + '&quot;&gt;' + config.messages.dates.months[i] + '&lt;/option&gt;';
	}
	formstring += '&lt;/select&gt;&amp;nbsp;&amp;nbsp;Day: &lt;select name=&quot;day&quot;&gt;&lt;option value=&quot;&quot;&gt;Every day&lt;/option&gt;';
	for (i = 1; i &lt; 32; i++)
	{
		formstring += '&lt;option' + ((i == (today.getDate() )) ? ' selected' : '') + ' value=&quot;' + i + '&quot;&gt;' + i + '&lt;/option&gt;';
	}
	formstring += '&lt;/select&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Reminder Title: &lt;input type=&quot;text&quot; size=&quot;40&quot; name=&quot;title&quot; value=&quot;&quot; onfocus=&quot;this.select();&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;input type=&quot;button&quot; value=&quot;Add reminder&quot; onclick=&quot;addReminderToTiddler(this.form)&quot;&gt;';
	formstring += '&lt;/form&gt;&lt;/html&gt;';
	
	var panel = config.macros.slider.createSlider(place, null, &quot;New Reminder&quot;, &quot;Open a form to add a new reminder to this tiddler&quot;);
	wikify(formstring, panel, null, store.getTiddler(params[1]));
};

// onclick: process input and insert reminder at 'marker'
window.addReminderToTiddler = function(form) {
	if (!window.story) 
	{
		window.story=window; 
	}
	if (!store.getTiddler) 
	{
		store.getTiddler=function(title) {return this.tiddlers[title];};
	}
	var title = window.story.findContainingTiddler(form).id.substr(7);
	var tiddler=store.getTiddler(title);
	var txt='\n&lt;&lt;reminder ';
	if (form.year.value != &quot;&quot;)
		txt += 'year:'+form.year.value + ' ';
	if (form.month.value != &quot;&quot;)
		txt += 'month:'+form.month.value + ' ';
	if (form.day.value != &quot;&quot;)
		txt += 'day:'+form.day.value + ' ';
	txt += 'title:&quot;'+form.title.value+'&quot; ';
	txt +='&gt;&gt;';
	tiddler.set(null,tiddler.text + txt);
	window.story.refreshTiddler(title,1,true);
	store.setDirty(true);
	// tomo - make sure to save if required
	if(config.options.chkAutoSave)
		saveChanges();
};

function hasTag(tiddlerTags, tagFilters)
{
	// tomo - this has been updated to work better with tag-less tiddlers
	
	//Make sure we respond well to empty tagFilterlists
	if (tagFilters.length==0) return true;
	
	var bHasTag = false;
	
	/*bNoPos says: &quot;'till now there has been no check using a positive filter&quot;
	Imagine a filterlist consisting of 1 negative filter:
	If the filter isn't matched, we want hasTag to be true.
	Yet bHasTag is still false ('cause only positive filters cause bHasTag to change)
	
	If no positive filters are present bNoPos is true, and no negative filters are matched so we have not returned false
	Thus: hasTag returns true.
	
	If at any time a positive filter is encountered, we want at least one of the tags to match it, so we turn bNoPos to false, which
	means bHasTag must be true for hasTag to return true*/
	var bNoPos=true;
	
	for (var t3 = 0; t3 &lt; tagFilters.length; t3++)
	{
		var negTest = tagFilters[t3].length &gt; 1 &amp;&amp; tagFilters[t3].charAt(0) == '!';
		// do the positive filter test outside of the tag loop, in case there are no tags!
		if (bNoPos &amp;&amp; !negTest) bNoPos = false;
		
		for(var t2=0; t2&lt;tiddlerTags.length; t2++)
		{
			if (negTest) 
			{
				if (tiddlerTags[t2] == tagFilters[t3].substring(1))
				{
					//If at any time a negative filter is matched, we return false
					return false;
				}
			}
			else 
			{
				if (tiddlerTags[t2] == tagFilters[t3])
				{
					//A positive filter is matched. As long as no negative filter is matched, hasTag will return true
					bHasTag=true;
				}
			}
		}
	}
	return (bNoPos || bHasTag);
};

//This function searches all tiddlers for the reminder  //macro.  It is intended that other plugins (like //calendar) will use this function to query for 
//upcoming reminders.
//The arguments to this function filter out reminders //based on when they will fire.
//
//ARGUMENTS:
//baseDate is the date that is used as &quot;now&quot;.  
//leadtime is a two element int array, with leadtime[0] 
//         as the lower bound and leadtime[1] as the
//         upper bound.  A reasonable default is [0,14]
//tags is a space-separated list of tags to use to filter 
//         tiddlers.  If a tag name begins with an !, then 
//         only tiddlers which do not have that tag will 
//         be considered.  For example &quot;examples holidays&quot;  
//         will search for reminders in any tiddlers that  
//         are tagged with examples or holidays and 
//         &quot;!examples !holidays&quot; will search for reminders 
//         in any tiddlers that are not tagged with 
//         examples or holidays.  Pass in null to search 
//         all tiddlers.
//limit.  If limit is null, individual reminders can 
//        override the leadtime specified earlier.  
//        Pass in 1 in order to override that behavior.

window.findTiddlersWithReminders = function findTiddlersWithReminders(baseDate, leadtime, tags, limit)
{
//function(searchRegExp,sortField,excludeTag)
//   var macroPattern = &quot;&lt;&lt;([^&gt;\\]+)(?:\\*)([^&gt;]*)&gt;&gt;&quot;;
   var macroPattern = &quot;&lt;&lt;(reminder)(.*)&gt;&gt;&quot;;
   var macroRegExp = new RegExp(macroPattern,&quot;mg&quot;);
   var matches = store.search(macroRegExp,&quot;title&quot;,&quot;&quot;);
   var arr = [];
   var tagsArray = null;
   if (tags != null)
   {
      tagsArray = tags.split(&quot; &quot;);
   }
   for(var t=matches.length-1; t&gt;=0; t--)
   {
      if (tagsArray != null)
      {
         //If they specified tags to filter on, and this tiddler doesn't 
	 //match, skip it entirely.
         if ( ! hasTag(matches[t].tags, tagsArray))
         {
            continue;
         }
      }

      var targetText = matches[t].text;
      do {
         // Get the next formatting match
         var formatMatch = macroRegExp.exec(targetText);
         if(formatMatch &amp;&amp; formatMatch[1] != null &amp;&amp; formatMatch[1].toLowerCase() == &quot;reminder&quot;)
         {
            //Find the matching date.
            
            var params = formatMatch[2] != null ? formatMatch[2].readMacroParams() : {};
            var dateHash = getParamsForReminder(params);
            if (limit != null || dateHash[&quot;leadtime&quot;] == null)
            {
               if (leadtime == null)
                   dateHash[&quot;leadtime&quot;] = leadtime;
               else
               {
                  dateHash[&quot;leadtime&quot;] = [];
                  dateHash[&quot;leadtime&quot;][0] = leadtime[0];
                  dateHash[&quot;leadtime&quot;][1] = leadtime[1];
               }
            }
	    if (dateHash[&quot;leadtime&quot;] == null)
               dateHash[&quot;leadtime&quot;] = config.macros.reminders[&quot;defaultLeadTime&quot;]; 
            var leadTimeLowerBound = baseDate.addDays(dateHash[&quot;leadtime&quot;][0]);
            var leadTimeUpperBound = baseDate.addDays(dateHash[&quot;leadtime&quot;][1]);
            var matchedDate = findDateForReminder(dateHash, baseDate, leadTimeLowerBound, leadTimeUpperBound);
            while (matchedDate != null)
            {
               var hash = {};
               hash[&quot;diff&quot;] = matchedDate.getDifferenceInDays(baseDate);
               hash[&quot;matchedDate&quot;] = new Date(matchedDate.getFullYear(), matchedDate.getMonth(), matchedDate.getDate(), 0, 0);
               hash[&quot;params&quot;] = cloneParams(dateHash);
               hash[&quot;tiddler&quot;] = matches[t].title;
               hash[&quot;tags&quot;] = matches[t].tags;
               arr.pushUnique(hash);
	       if (dateHash[&quot;recurdays&quot;] != null || (dateHash[&quot;year&quot;] == null))
	       {
	         leadTimeLowerBound = leadTimeLowerBound.addDays(matchedDate.getDifferenceInDays(leadTimeLowerBound)+ 1);
                 matchedDate = findDateForReminder(dateHash, baseDate, leadTimeLowerBound, leadTimeUpperBound);
	       }
	       else matchedDate = null;
            }
         }
      }while(formatMatch);
   }
   if(arr.length &gt; 1)  //Sort the array by number of days remaining.
   {
      arr.sort(function (a,b) {if(a[&quot;diff&quot;] == b[&quot;diff&quot;]) {return(0);} else {return (a[&quot;diff&quot;] &lt; b[&quot;diff&quot;]) ? -1 : +1; } });
   }
   return arr;
};

//This function takes the reminder macro parameters and
//generates the string that is used for display.
//This function is not intended to be called by 
//other plugins.
 window.getReminderMessageForDisplay= function getReminderMessageForDisplay(diff, params, matchedDate, tiddlerTitle)
{
   var anniversaryString = &quot;&quot;;
   var reminderTitle = params[&quot;title&quot;];
   if (reminderTitle == null)
   {
      reminderTitle = config.macros.reminders[&quot;untitledReminder&quot;];
   }
   if (params[&quot;firstyear&quot;] != null)
   {
      anniversaryString = config.macros.reminders[&quot;defaultAnniversaryMessage&quot;].replace(&quot;DIFF&quot;, (matchedDate.getFullYear() - params[&quot;firstyear&quot;]));
   }
   var mess = &quot;&quot;;
   var diffString = &quot;&quot;;
   if (diff == 0)
   {
      diffString = config.macros.reminders[&quot;todayString&quot;];
   }
   else if (diff == 1)
   {
      diffString = config.macros.reminders[&quot;tomorrowString&quot;];
   }
   else
   {
      diffString = config.macros.reminders[&quot;ndaysString&quot;].replace(&quot;DIFF&quot;, diff);
   }
   var format = config.macros.reminders[&quot;defaultReminderMessage&quot;];
   if (params[&quot;format&quot;] != null)
   {
      format = params[&quot;format&quot;];
   }
   mess = format;
//HACK!  -- Avoid replacing DD in TIDDLER with the date
   mess = mess.replace(/TIDDLER/g, &quot;TIDELER&quot;);
   mess = matchedDate.formatStringDateOnly(mess);
   mess = mess.replace(/TIDELER/g, &quot;TIDDLER&quot;);
   if (tiddlerTitle != null)
   {
      mess = mess.replace(/TIDDLERNAME/g, tiddlerTitle);
      mess = mess.replace(/TIDDLER/g, &quot;[[&quot; + tiddlerTitle + &quot;]]&quot;);
   }
   
   mess = mess.replace(&quot;DIFF&quot;, diffString).replace(&quot;TITLE&quot;, reminderTitle).replace(&quot;DATE&quot;, matchedDate.formatString(&quot;DDD MMM DD, YYYY&quot;)).replace(&quot;ANNIVERSARY&quot;, anniversaryString);
   return mess;
};

// Parse out the macro parameters into a hashtable.  This
// handles the arguments for reminder, showReminders and 
// displayTiddlersWithReminders.
window.getParamsForReminder = function getParamsForReminder(params)
{
   var dateHash = {};
   var type = &quot;&quot;;
   var num = 0;
   var title = &quot;&quot;;
   for(var t=0; t&lt;params.length; t++)
   {
      var split = params[t].split(&quot;:&quot;);
      type = split[0].toLowerCase();
      var value = split[1];
      for (var i=2; i &lt; split.length; i++)
      {
         value += &quot;:&quot; + split[i];
      }
      if (type == &quot;nolinks&quot; || type == &quot;limit&quot; || type == &quot;hidden&quot;)
      {
         num = 1;
      }
      else if (type == &quot;leadtime&quot;)
      {
         var leads = value.split(&quot;...&quot;);
         if (leads.length == 1)
         {
            leads[1]= leads[0];
            leads[0] = 0;
         }
         leads[0] = parseInt(leads[0], 10);
         leads[1] = parseInt(leads[1], 10);
         num = leads;
      }
      else if (type == &quot;offsetdayofweek&quot;)
      {
          if (value.substr(0,1) == &quot;-&quot;)
          {
             dateHash[&quot;negativeOffsetDayOfWeek&quot;] = 1;
	     value = value.substr(1);
          }
          num = parseInt(value, 10);
      }
      else if (type != &quot;title&quot; &amp;&amp; type != &quot;tag&quot; &amp;&amp; type != &quot;format&quot;)
      {
         num = parseInt(value, 10);
      }
      else
      {
         title = value;
         t++;
         while (title.substr(0,1) == '&quot;' &amp;&amp; title.substr(title.length - 1,1) != '&quot;' &amp;&amp; params[t] != undefined)
         {
            title += &quot; &quot; + params[t++];
         }
         //Trim off the leading and trailing quotes
         if (title.substr(0,1) == &quot;\&quot;&quot; &amp;&amp; title.substr(title.length - 1,1)== &quot;\&quot;&quot;)
         {
            title = title.substr(1, title.length - 2);
            t--;
         }
         num = title;
      }
      dateHash[type] = num;
   }
   //date is synonymous with day
   if (dateHash[&quot;day&quot;] == null)
   {
      dateHash[&quot;day&quot;] = dateHash[&quot;date&quot;];
   }
   return dateHash;
};

//This function finds the date specified in the reminder 
//parameters.  It will return null if no match can be
//found.  This function is not intended to be used by
//other plugins.
window.findDateForReminder= function findDateForReminder( dateHash, baseDate, leadTimeLowerBound, leadTimeUpperBound)
{
   if (baseDate == null)
   {
     baseDate = new Date().getMidnight();
   }
   var hashKey = baseDate.convertToYYYYMMDDHHMM();
   for (var k in dateHash)
   {
      hashKey += &quot;,&quot; + k + &quot;|&quot; + dateHash[k];
   }
   hashKey += &quot;,&quot; + leadTimeLowerBound.convertToYYYYMMDDHHMM();
   hashKey += &quot;,&quot; + leadTimeUpperBound.convertToYYYYMMDDHHMM();
   if (reminderCache[hashKey] == null)
   {
      //If we don't find a match in this run, then we will
      //cache that the reminder can't be matched.
      reminderCache[hashKey] = false;
   }
   else if (reminderCache[hashKey] == false)
   {
      //We've already tried this date and failed
      return null;
   }
   else
   {
      return reminderCache[hashKey];
   }
   
   var bOffsetSpecified = dateHash[&quot;offsetyear&quot;] != null || 
				dateHash[&quot;offsetmonth&quot;] != null || 
				dateHash[&quot;offsetday&quot;] != null || 
				dateHash[&quot;offsetdayofweek&quot;] != null || 
				dateHash[&quot;recurdays&quot;] != null;
   
   // If we are matching the base date for a dayofweek offset, look for the base date a 
   //little further back.
   var tmp1leadTimeLowerBound = leadTimeLowerBound;  
   if ( dateHash[&quot;offsetdayofweek&quot;] != null)
   {
      tmp1leadTimeLowerBound = leadTimeLowerBound.addDays(-6);  
   }
   var matchedDate = baseDate.findMatch(dateHash, tmp1leadTimeLowerBound, leadTimeUpperBound);
   if (matchedDate != null)
   {
      var newMatchedDate = matchedDate;
      if (dateHash[&quot;recurdays&quot;] != null)
      {
         while (newMatchedDate.getTime() &lt; leadTimeLowerBound.getTime())
         {
            newMatchedDate = newMatchedDate.addDays(dateHash[&quot;recurdays&quot;]);
         }
      }
      else if (dateHash[&quot;offsetyear&quot;] != null || 
		dateHash[&quot;offsetmonth&quot;] != null || 
		dateHash[&quot;offsetday&quot;] != null || 
		dateHash[&quot;offsetdayofweek&quot;] != null)
      {
         var tmpdateHash = cloneParams(dateHash);
         tmpdateHash[&quot;year&quot;] = dateHash[&quot;offsetyear&quot;];
         tmpdateHash[&quot;month&quot;] = dateHash[&quot;offsetmonth&quot;];
         tmpdateHash[&quot;day&quot;] = dateHash[&quot;offsetday&quot;];
         tmpdateHash[&quot;dayofweek&quot;] = dateHash[&quot;offsetdayofweek&quot;];
	 var tmpleadTimeLowerBound = leadTimeLowerBound;
	 var tmpleadTimeUpperBound = leadTimeUpperBound;
	 if (tmpdateHash[&quot;offsetdayofweek&quot;] != null)
	 {
	 	if (tmpdateHash[&quot;negativeOffsetDayOfWeek&quot;] == 1)
		{
		   tmpleadTimeLowerBound = matchedDate.addDays(-6);
		   tmpleadTimeUpperBound = matchedDate;

		}
		else
		{
		   tmpleadTimeLowerBound = matchedDate;
		   tmpleadTimeUpperBound = matchedDate.addDays(6);
		}

	 }
	 newMatchedDate = matchedDate.findMatch(tmpdateHash, tmpleadTimeLowerBound, tmpleadTimeUpperBound);
         //The offset couldn't be matched.  return null.
         if (newMatchedDate == null)
         {
            return null;
         }
      }
      if (newMatchedDate.isBetween(leadTimeLowerBound, leadTimeUpperBound))
      {
         reminderCache[hashKey] = newMatchedDate;
         return newMatchedDate;
      }
   }
   return null;
};

//This does much the same job as findDateForReminder, but
//this one doesn't deal with offsets or recurring 
//reminders.
Date.prototype.findMatch = function findMatch(dateHash, leadTimeLowerBound, leadTimeUpperBound)
{

   var bSpecifiedYear =     (dateHash[&quot;year&quot;] != null);
   var bSpecifiedMonth =     (dateHash[&quot;month&quot;] != null);
   var bSpecifiedDay =     (dateHash[&quot;day&quot;] != null);
   var bSpecifiedDayOfWeek =     (dateHash[&quot;dayofweek&quot;] != null);
   if (bSpecifiedYear &amp;&amp; bSpecifiedMonth &amp;&amp; bSpecifiedDay)
   {
      return new Date(dateHash[&quot;year&quot;], dateHash[&quot;month&quot;]-1, dateHash[&quot;day&quot;], 0, 0);
   }
   var bMatchedYear = !bSpecifiedYear;
   var bMatchedMonth = !bSpecifiedMonth;
   var bMatchedDay = !bSpecifiedDay;
   var bMatchedDayOfWeek = !bSpecifiedDayOfWeek;
   if (bSpecifiedDay &amp;&amp; bSpecifiedMonth &amp;&amp; !bSpecifiedYear &amp;&amp; !bSpecifiedDayOfWeek)
   {

      //Shortcut -- First try this year.  If it's too small, try next year.
      var tmpMidnight = this.getMidnight();
      var tmpDate = new Date(this.getFullYear(), dateHash[&quot;month&quot;]-1, dateHash[&quot;day&quot;], 0,0);
      if (tmpDate.getTime() &lt; leadTimeLowerBound.getTime())
      {
         tmpDate = new Date((this.getFullYear() + 1), dateHash[&quot;month&quot;]-1, dateHash[&quot;day&quot;], 0,0);
      }
      if ( tmpDate.isBetween(leadTimeLowerBound, leadTimeUpperBound))
      {
         return tmpDate;
      }
      else
      {
         return null;
      }
   }

   var newDate = leadTimeLowerBound; 
   while (newDate.isBetween(leadTimeLowerBound, leadTimeUpperBound))
   {
      var tmp = testDate(newDate, dateHash, bSpecifiedYear, bSpecifiedMonth, bSpecifiedDay, bSpecifiedDayOfWeek);
      if (tmp != null)
        return tmp;
      newDate = newDate.addDays(1);
   }
};

function testDate(testMe, dateHash, bSpecifiedYear, bSpecifiedMonth, bSpecifiedDay, bSpecifiedDayOfWeek)
{
   var bMatchedYear = !bSpecifiedYear;
   var bMatchedMonth = !bSpecifiedMonth;
   var bMatchedDay = !bSpecifiedDay;
   var bMatchedDayOfWeek = !bSpecifiedDayOfWeek;
   if (bSpecifiedYear)
   {
      bMatchedYear = (dateHash[&quot;year&quot;] == testMe.getFullYear());
   }
   if (bSpecifiedMonth)
   {
      bMatchedMonth = ((dateHash[&quot;month&quot;] - 1)  == testMe.getMonth() );
   }
   if (bSpecifiedDay)
   {
      bMatchedDay = (dateHash[&quot;day&quot;] == testMe.getDate());
   }
   if (bSpecifiedDayOfWeek)
   {
      bMatchedDayOfWeek = (dateHash[&quot;dayofweek&quot;] == testMe.getDay());
   }

   if (bMatchedYear &amp;&amp; bMatchedMonth &amp;&amp; bMatchedDay &amp;&amp; bMatchedDayOfWeek)
   {
      return testMe;
   }
};

//Returns true if the date is in between two given dates
Date.prototype.isBetween = function isBetween(lowerBound, upperBound)
{
  return (this.getTime() &gt;= lowerBound.getTime() &amp;&amp; this.getTime() &lt;= upperBound.getTime());
}
//Return a new date, with the time set to midnight (0000)
Date.prototype.getMidnight = function getMidnight()
{
   return new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0);
};
// Add the specified number of days to a date.
Date.prototype.addDays = function addDays(numberOfDays)
{
   return new Date(this.getFullYear(), this.getMonth(), this.getDate() + numberOfDays, 0, 0);
};
//Return the number of days between two dates.
Date.prototype.getDifferenceInDays = function getDifferenceInDays(otherDate)
{
//I have to do it this way, because this way ignores daylight savings
   var tmpDate = this.addDays(0);
   if (this.getTime() &gt; otherDate.getTime())
   {
      var i = 0;
      for (i = 0; tmpDate.getTime() &gt; otherDate.getTime(); i++)
      {
         tmpDate = tmpDate.addDays(-1);
      }
      return i;
   }
   else
   {
      var i = 0;
      for (i = 0; tmpDate.getTime() &lt; otherDate.getTime(); i++)
      {
         tmpDate = tmpDate.addDays(1);
      }
      return i * -1;
   }
   return 0;
};
function cloneParams(what) {
    var tmp = {};
    for (var i in what) {
        tmp[i] = what[i];
    }
    return tmp;
}
// Substitute date components into a string
Date.prototype.formatStringDateOnly = function formatStringDateOnly(template)
{
	template = template.replace(&quot;YYYY&quot;,this.getFullYear());
	template = template.replace(&quot;YY&quot;,String.zeroPad(this.getFullYear()-2000,2));
	template = template.replace(&quot;MMM&quot;,config.messages.dates.months[this.getMonth()]);
	template = template.replace(&quot;0MM&quot;,String.zeroPad(this.getMonth()+1,2));
	template = template.replace(&quot;MM&quot;,this.getMonth()+1);
	template = template.replace(&quot;DDD&quot;,config.messages.dates.days[this.getDay()]);
	template = template.replace(&quot;0DD&quot;,String.zeroPad(this.getDate(),2));
	template = template.replace(&quot;DD&quot;,this.getDate());
	return template;
};

//}}}</pre>
</div>
<div title="Reminders" modifier="TomO" created="200603020500" modified="200611211411" tags="gtd">
<pre>!Overdue actions
&lt;&lt;showReminders leadtime:-365...-1 tag:&quot;action !done&quot; format:&quot;DATE: @@color:red;TITLE@@ [ TIDDLER ]&quot;&gt;&gt;
!Actions for today and tomorrow
&lt;&lt;showReminders leadtime:0...1 tag:&quot;action !done&quot; format:&quot;DIFF: @@color:red;TITLE@@ [ TIDDLER ]&quot;&gt;&gt;
!All reminders for the next week
&lt;&lt;showReminders leadtime:0...7 tag:&quot;!done&quot; format:&quot;DIFF: @@color:red;TITLE@@ [ TIDDLER ]&quot;&gt;&gt;</pre>
</div>
<div title="SQLログを表示する。" modifier="ImaiMasanobu" created="200905190826" modified="200905190835" tags="tips" changecount="3" creator="ImaiMasanobu">
<pre>DataSource.groovyに以下の設定を行う。
{{{
  dataSource {
    dbCreate = &quot;create-drop&quot;
    url = &quot;jdbc:hsqldb:mem:devDB&quot;
    logSql = true  // &lt;- これ
}}}</pre>
</div>
<div title="SampleTicklers" modifier="TomO" created="200612061940" modified="200612061947" tags="gtd">
<pre>Click on the button below to create a new reminder as a tickler:

&lt;&lt;newReminder&gt;&gt;</pre>
</div>
<div title="SiteSubtitle" modifier="TomO" created="200603030000" modified="200809301757" changecount="2">
<pre>do it, delegate it, or defer it</pre>
</div>
<div title="SiteTitle" modifier="TomO" created="200603030000" modified="200603030000">
<pre>d^^3^^</pre>
</div>
<div title="Someday-Maybe" modifier="TomO" created="200603020500" modified="200610220108" tags="gtd">
<pre>&lt;&lt;list tagged txtGTDSomedayContext any&gt;&gt;
</pre>
</div>
<div title="StyleSheet" modifier="TomO" created="200603020500" modified="200806282237" tags="systemTiddler">
<pre>[[GTDStyleSheet]]
[[GTDTWStyleSheet]]

/***
!Personal preferences
***/

/*{{{*/
/* make input fields in viewer (options) show up in correct size */
.viewer input { font-size: 0.9em; }

.editor { font-family: monospace; font-size: 10pt; }

/*}}}*/

</pre>
</div>
<div title="StyleSheetPrint" modifier="TomO" created="200603020500" modified="200603020500" tags="systemTiddler">
<pre>/*{{{*/

@media print {
#mainMenu, #sidebar, #messageArea {display: none !important;}
#displayArea {margin: 1em 1em 0em 1em;}


/* LAYOUT ELEMENTS ========================================================== */
*
{
 margin: 0;
 padding: 0;
}

body {
 background: #fff;
 color: #000;
 font-size: 6.2pt;
 font-family: &quot;Lucida Grande&quot;, &quot;Bitstream Vera Sans&quot;, Helvetica, Verdana, Arial, sans-serif;
}

img {
 max-width: 2.2in;
 max-height: 4.3in;
}

#header, #side_container, #storeArea, #copyright, #floater, #messageArea, .save_accesskey, .site_description, #saveTest, .toolbar, .footer
{
 display: none;
}

#tiddlerDisplay, #displayArea
{
 display: inline;
}

.tiddler {
 margin: 0 0 2em 0;
 border-top: 1px solid #000;
 page-break-before: always;
}

.tiddler:first-child {
 page-break-before: avoid;
}

.title {
 font-size: 1.6em;
 font-weight: bold;
 margin-bottom: .3em;
 padding: .2em 0;
 border-bottom: 1px dotted #000;
}

p, blockquote, ul, li, ol, dt, dd, dl, table
{
 margin: 0 0 .3em 0;
}

h1, h2, h3, h4, h5, h6
{
 margin: .2em 0;
} 

h1
{
 font-size: 1.5em;
}

h2
{
 font-size: 1.3em;
}

h3
{
 font-size: 1.25em;
}

h4
{
 font-size: 1.15em;
}

h5
{
 font-size: 1.1em;
}

blockquote
{
 margin: .6em;
 padding-left: .6em;
 border-left: 1px solid #ccc;
}

ul
{
 list-style-type: circle;
}

li
{
 margin: .1em 0 .1em 2em;
 line-height: 1.4em; 
}

table
{
 border-collapse: collapse;
 font-size: 1em;
}

td, th
{
 border: 1px solid #999;
 padding: .2em;
}

hr {
 border: none;
 border-top: dotted 1px #777;
 height: 1px;
 color: #777;
 margin: .6em 0;
}
}
/*}}}*/</pre>
</div>
<div title="Summary Review" modifier="TomO" created="200610121907" modified="200611291313" tags="gtd review">
<pre>|review|k
|!Overdue actions|!All reminders for the next week|
|&lt;&lt;showReminders leadtime:-365...-1 tag:&quot;action !done&quot; format:&quot;DATE: @@color:red;TITLE@@ [ TIDDLER ]&quot;&gt;&gt;|&lt;&lt;showReminders leadtime:0...7 tag:&quot;!done&quot; format:&quot;DIFF: @@color:red;TITLE@@ [ TIDDLER ]&quot;&gt;&gt;|
|!Actions for today/tomorrow|~|
|&lt;&lt;showReminders leadtime:0...1 tag:&quot;action !done&quot; format:&quot;DIFF: @@color:red;TITLE@@ [ TIDDLER ]&quot;&gt;&gt;|~|
|!Project Review|!Action Review|
|&lt;&lt;tiddler &quot;Project Review&quot;&gt;&gt;|&lt;&lt;tiddler &quot;Action Review&quot;&gt;&gt;|
</pre>
</div>
<div title="Ticklers" modifier="ImaiMasanobu" created="200905190827" changecount="2" creator="ImaiMasanobu">
<pre>Type the text for 'Ticklers'</pre>
</div>
<div title="TiddlyTools Server" modifier="TomO" created="200807101851" modified="200807101851" tags="systemServer excludeLists">
<pre>|''URL:''|http://www.tiddlytools.com/|
|''Description:''|TiddlyTools - Small Tools for Big Ideas|
|''Author:''|Eric Shulman|</pre>
</div>
<div title="UC1.あるユーザの投稿リストを見る" modifier="ImaiMasanobu" created="200905150734" modified="200905190202" tags="project usecase done" changecount="11" creator="ImaiMasanobu" gtd.context="@unfiled">
<pre># アクターは，あるユーザのIDをURLに含む投稿リスト画面を要求する
# システムは，IDで指定されたユーザの投稿を新しいものから順に並べて20件ずつ表示する
----

&lt;&lt;gtdAction &quot;自動リンク&quot; &quot;@unfiled&quot;&gt;&gt;
&lt;&lt;gtdAction &quot;ステータスタイムラインを表示する（基本）&quot; &quot;unfiled&quot;&gt;&gt;
&lt;&lt;gtdAction &quot;ステータスタイムラインをAJAX化する&quot; &quot;unfiled&quot;&gt;&gt;</pre>
</div>
<div title="UC2.個別投稿を見る" modifier="ImaiMasanobu" created="200905150736" modified="200905190150" tags="project usecase done" changecount="11" creator="ImaiMasanobu" gtd.context="@unfiled">
<pre># アクターは，あるユーザのIDと投稿IDをURLに含む個別投稿画面を要求する
# システムは，指定されたユーザの指定された投稿を表示する。
----

&lt;&lt;gtdAction &quot;個別投稿を見る（基本）&quot; &quot;unfiled&quot;&gt;&gt;
</pre>
</div>
<div title="UC3.ログインする" modifier="ImaiMasanobu" created="200905150738" modified="200905190154" tags="project usecase done" changecount="12" creator="ImaiMasanobu" gtd.context="@unfiled">
<pre>前提条件：アクターがユーザとしてシステムにログインしていないこと
# アクターは，ユーザIDとパスワードを入力する
# システムは，ユーザIDとパスワードが正しいことを確認した後，ユーザのタイムライン画面を表示する
事後条件：アクターがユーザとしてシステムにログインしていること
----
&lt;&lt;gtdAction &quot;ユーザ登録&quot; &quot;@unfiled&quot;&gt;&gt;
&lt;&lt;gtdAction &quot;プロフィール&quot; &quot;@unfiled&quot;&gt;&gt;
&lt;&lt;gtdAction &quot;ログイン／ログアウトできる&quot; &quot;unfiled&quot;&gt;&gt;
&lt;&lt;gtdAction &quot;アイコン登録&quot; &quot;@unfiled&quot;&gt;&gt;</pre>
</div>
<div title="UC4.タイムラインを見る" modifier="ImaiMasanobu" created="200905150740" modified="200905190156" tags="project usecase done" changecount="12" creator="ImaiMasanobu" gtd.context="@unfiled">
<pre>事前条件：アクターがユーザとしてシステムにログインしていること。
# アクターは，タイムライン画面を要求する
# システムは，アクターであるユーザの投稿と，アクターがお気に入りに追加しているユーザの投稿を新しいものから順に並べて20件ずつ表示する
----
&lt;&lt;gtdAction &quot;投稿の削除&quot; &quot;@unfiled&quot;&gt;&gt;
&lt;&lt;gtdAction &quot;タイムラインを見る（基本）&quot; &quot;unfiled&quot;&gt;&gt;</pre>
</div>
<div title="UC5.投稿する" modifier="ImaiMasanobu" created="200905150743" modified="200905190204" tags="project usecase done" changecount="13" creator="ImaiMasanobu" gtd.context="@unfiled">
<pre>事前条件：アクターがユーザとしてシステムにログインしていること。タイムライン画面が表示されていること。
# アクターは，テキストを入力して投稿指示をする
# システムは，テキストが空や空白でない場合，ユーザIDと投稿時間とともに投稿されたテキストを保存する
## テキストが空や空白だった場合は，そのまま次へ進む
## テキストが140文字を越えていた場合は，エラーメッセージを表示する
# システムは，アクターであるユーザの投稿と，アクターがお気に入りに追加しているユーザの投稿を新しいものから順に並べて20件ずつ表示する

----
&lt;&lt;gtdAction &quot;メッセージ機能&quot; &quot;@unfiled&quot;&gt;&gt;
&lt;&lt;gtdAction &quot;ステータス投稿（基本）&quot; &quot;unfiled&quot;&gt;&gt;
&lt;&lt;gtdAction &quot;ステータス投稿をAJAX化する&quot; &quot;unfiled&quot;&gt;&gt;</pre>
</div>
<div title="UC6.あるユーザをお気に入りに追加する" modifier="ImaiMasanobu" created="200905150746" modified="200905190213" tags="project usecase done" changecount="12" creator="ImaiMasanobu" gtd.context="@unfiled">
<pre>事前条件：アクターがユーザとしてシステムにログインしていること。アクターがお気に入りに追加していないユーザの投稿リスト画面が表示されていること
# アクターは，システムに対してあるユーザをお気に入りに追加する指示をする
# システムは，指定されたユーザをアクターのお気に入りリストに追加する
事後条件：お気に入りに追加したユーザについてはお気に入り削除の指示のみ出せるようになること。
&lt;&lt;gtdAction &quot;お気に入り一覧表示&quot; &quot;@unfiled&quot;&gt;&gt;
&lt;&lt;gtdAction &quot;お気に入り一部表示&quot; &quot;@unfiled&quot;&gt;&gt;
&lt;&lt;gtdAction &quot;お気に入りの設定&quot; &quot;unfiled&quot;&gt;&gt;</pre>
</div>
<div title="UC7.あるユーザをお気に入りから削除する" modifier="ImaiMasanobu" created="200905150750" modified="200905190217" tags="project usecase done" changecount="11" creator="ImaiMasanobu" gtd.context="@unfiled">
<pre>事前条件：アクターがユーザとしてシステムにログインしていること。アクターがお気に入りに追加しているユーザの投稿リスト画面が表示されていること。
# アクターは，システムに対してあるユーザをお気に入りから削除する指示をする
# システムは，指定されたユーザをアクターのお気に入りリストから削除する
事後条件：お気に入りから削除したユーザについてはお気に入り追加の指示のみ出せるようになること。
&lt;&lt;gtdAction &quot;フォロアーの削除&quot; &quot;unfiled&quot;&gt;&gt;</pre>
</div>
<div title="UpdateApplication" modifier="TomO" created="200603282052" modified="200810252153" tags="gtd" changecount="11">
<pre>If you are connected to the Internet, you can always get the latest version of this application. While it is possible to upgrade using the standard TiddlyWiki import using the backstage area (for which there is an official d-cubed feed, but see the caveats for upgrading using backstage, below), there are ''three better'' ways you can do this:

Click the following button if you simply want to get the latest changes to any of the core tiddlers that make up this application. These tiddlers are tagged &quot;gtd&quot;, and updating in this way will not overwrite any of the core tiddlers that you may have changed unless the core tiddlers are even newer than your changes. This is the recommended way to get updates:
**&lt;&lt;importUpdates &quot;http://www.dcubed.ca/d3-update-13.html&quot;&gt;&gt;

Click the following button if you would like to get the latest changes to any of the core tiddlers, but to interactively approve each and every updated tiddler as it is loaded into your system. If there are no updated tiddlers, you will not be prompted and the update will exit quietly:
**&lt;&lt;importUpdates &quot;http://www.dcubed.ca/d3-update-13.html&quot; updates &quot;Update interactively&quot; &quot;Click here to interactively update the application&quot; confirm&gt;&gt;

Click the following button if you want to download all of the core tiddlers, regardless of their modification date. Use this to absolutely ensure that you are running with the core application as it was originally written:
**&lt;&lt;importUpdates &quot;http://www.dcubed.ca/d3-update-13.html&quot; all &quot;Update everything&quot; force&gt;&gt;

''For your safety, your file will be saved and a backup file will be automatically generated before any update is performed.''

!!Updating using backstage

There is a feed defined to enable you to update from &quot;backstage&quot;. Just select &quot;Official d-cubed feed&quot; from the feed selection list, and import all tiddlers. Be sure to uncheck the &quot;Keep these tiddlers linked...&quot; checkbox before importing.

Also, unless you are importing into an empty d-cubed document, always save a copy before importing into a document that has important stuff.
</pre>
</div>
<div title="Upgrading from version 1.2" modifier="TomO" created="200810252053" modified="200810262153" tags="gtd" changecount="14">
<pre>Because of the changes to TiddlyWiki, it is not possible to upgrade to this new version from version 1.2 using the normal UpdateApplication you are familiar with. Indeed, the only way to get this new version is to start with an empty d-cubed document, and import your projects, actions, and contexts from your existing d-cubed document.

The most reliable way to import your tiddlers into a new document is to use the &quot;backstage import&quot; feature of the new TiddlyWiki. If you are not familiar with this, you should take a few moments at http://www.tiddlywiki.com/ to learn the new features. However please note that ''whenever you are using the backstage import feature of TiddlyWiki to import actions, projects, or contexts, you must first disable the &quot;GTDPlugins&quot; plugin''. 

To reliably upgrade your d-cubed document (subject to caveats, below):
# Start with a copy of an empty d-cubed document (you will need the empty document later).
# Open the &quot;backstage&quot;, click on the &quot;plugins&quot; tab, and ensure there is a check in the &quot;Disabled&quot; column of &quot;GTDPlugins&quot;. Save and reload your document.
# Import all the tiddlers from your old d-cubed document. Be sure to have the &quot;Keep these tiddlers linked...&quot; checkbox //unchecked//.
# After importing all the tiddlers from your original d-cubed document ''but before saving and reloading'', do another import from the original empty d-cubed document you copied in step 1. Again, ensure that the &quot;Keep these tiddlers linked...&quot; checkbox is //unchecked//.
# Once you have completed your import, confirm that &quot;GTDPlugins&quot; is re-enabled, reversing step 2. Save and reload, and you should be back in business. 
# Remove or disable the following obsolete plugins: TWUpdatePlugin, NewerTiddlerPlugin, ImportTiddlersPlugin. If you have customized core pieces of d-cubed (such as GTDMenu or ProjectList) you can now also carefully merge those changes into your new working document.
If you do not follow this procedure, especially disabling &quot;GTDPlugins&quot;, you will see strange behaviour as the core d-cubed code tries to process your GTD tiddlers as they are being pulled in. However once you are upgraded you can freely use the backstage import at any time in the future to get other non-d3 tiddlers (like other plugins) without disabling &quot;GTDPlugins&quot;. 

While the upgrade process described above is rather inelegant, the good news is that unless TiddlyWiki undergoes another major architectural change, future updates to d-cubed will revert to the usual painless process in UpdateApplication.

!!Platform caveats
As of this writing, version 2.4.1 of TiddlyWiki has a number of issues with certain browser/platform configurations:
#On Firefox for Mac, you need to ensure that the source and destination d-cubed documents for the import are in the same directory. Otherwise, you get some pretty messy behaviour in the file path selection.
#On IE (Windows), file importing is simply broken (&quot;access is denied&quot;). There does not appear to be a workaround save using Firefox (for the import at least).</pre>
</div>
<div title="actionEditTemplate" modifier="TomO" created="200603222115" modified="200705281249" tags="gtd template">
<pre>&lt;div class='toolbar' macro='toolbar projectify +saveTiddler -cancelTiddler deleteAction'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='subtitle'&gt;&lt;span macro='view gtd.project'&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit tags'&gt;&lt;/div&gt;
&lt;div class='editorFooter'&gt;&lt;span macro='message views.editor.tagPrompt'&gt;&lt;/span&gt;&lt;span macro='tagChooser'&gt;&lt;/span&gt;&lt;/div&gt;</pre>
</div>
<div title="actionViewTemplate" modifier="TomO" created="200603020500" modified="200806290155" tags="gtd template">
<pre>&lt;div class='toolbar' macro='toolbar changeContext changeProject deleteAction closeTiddler closeOthers +editTiddler permalink references jump'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='subtitle'&gt;&lt;span macro='view gtd.project link'&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class='subtitle'&gt;&lt;span macro='view modified date [[DD MMM YYYY]]'&gt;&lt;/span&gt; (created &lt;span macro='view created date [[DD MMM YYYY]]'&gt;&lt;/span&gt;)&amp;nbsp;&lt;span macro='gtdActionCompleted'&gt;&lt;/span&gt;complete&amp;nbsp;&lt;span macro='gtdToggleTag floating'&gt;&lt;/span&gt;floating&lt;/div&gt;
&lt;div class='tagging' macro='tagging'&gt;&lt;/div&gt;
&lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
&lt;div class='viewer' macro='view text wikified'&gt;&lt;/div&gt;
&lt;div class='tagClear'&gt;&lt;/div&gt;&lt;br/&gt;
&lt;div macro='newReminder'&gt;&lt;/div&gt;
</pre>
</div>
<div title="contextEditTemplate" modifier="TomO" created="200603180417" modified="200603212240" tags="gtd template">
<pre>&lt;div class='toolbar' macro='toolbar +saveTiddler -cancelTiddler deleteContext'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit tags'&gt;&lt;/div&gt;&lt;div class='editorFooter'&gt;&lt;span macro='message views.editor.tagPrompt'&gt;&lt;/span&gt;&lt;span macro='tagChooser'&gt;&lt;/span&gt;&lt;/div&gt;</pre>
</div>
<div title="contextViewTemplate" modifier="TomO" created="200603020500" modified="200612152030" tags="gtd template">
<pre>&lt;div class='toolbar' macro='toolbar newAction closeTiddler closeOthers +editTiddler permalink references jump'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='tagging'&gt;&lt;span class='subtitle'&gt;&lt;span macro='gtdToggleState chkGTDActionListReviewMode'&gt;&lt;/span&gt;Review next actions only&lt;/span&gt;&lt;/div&gt;
&lt;div class='viewer' macro='view text wikified'&gt;&lt;/div&gt;
&lt;div class='tagClear'&gt;&lt;/div&gt;</pre>
</div>
<div title="d3 settings" modifier="TomO" created="200612152131" modified="200612152131" tags="excludeLists">
<pre>Completed document conversion. Do not delete this tiddler unless you want to rebuild the action metadata.

This tiddler also contains document-specific preferences which, if deleted, will revert to default settings.</pre>
</div>
<div title="projectEditTemplate" modifier="TomO" created="200603171938" modified="200603212240" tags="gtd template">
<pre>&lt;div class='toolbar' macro='toolbar +saveTiddler -cancelTiddler deleteProject deleteProjectAll'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit tags'&gt;&lt;/div&gt;&lt;div class='editorFooter'&gt;&lt;span macro='message views.editor.tagPrompt'&gt;&lt;/span&gt;&lt;span macro='tagChooser'&gt;&lt;/span&gt;&lt;/div&gt;</pre>
</div>
<div title="projectViewTemplate" modifier="TomO" created="200603020500" modified="200612152030" tags="gtd template">
<pre>&lt;div class='toolbar' macro='toolbar newProjectAction archiveProject deleteProject deleteProjectAll closeTiddler closeOthers +editTiddler permalink references jump'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='subtitle'&gt;&lt;!-- &lt;span macro='view modifier link'&gt;&lt;/span&gt;, --&gt;&lt;span macro='view modified date [[DD MMM YYYY]]'&gt;&lt;/span&gt; (created &lt;span macro='view created date [[DD MMM YYYY]]'&gt;&lt;/span&gt;)&amp;nbsp;&lt;span macro='gtdToggleTag important'&gt;&lt;/span&gt;important&amp;nbsp;&lt;span macro='gtdToggleTag someday'&gt;&lt;/span&gt;defer&lt;/div&gt;
&lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
&lt;div class='viewer' macro='view text wikified'&gt;&lt;/div&gt;
&lt;div class='tagClear'&gt;&lt;/div&gt;&lt;br/&gt;&lt;div macro='newReminder'&gt;&lt;/div&gt;</pre>
</div>
<div title="reviewViewTemplate" modifier="TomO" created="200610051903" modified="200612152031" tags="gtd template">
<pre>&lt;div class='toolbar' macro='toolbar closeTiddler closeOthers +editTiddler permalink references jump'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;&lt;div class='subtitle' macro='today &quot;DDD, MMM DD, YYYY hh:0mm&quot;'&gt;&lt;/div&gt;
&lt;div class='viewer' macro='view text wikified'&gt;&lt;/div&gt;
&lt;div class='tagClear'&gt;&lt;/div&gt;</pre>
</div>
<div title="slide.Grails Plugin" modifier="ImaiMasanobu" created="200906240638" tags="slide" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" changecount="5" creator="ImaiMasanobu">
<pre>!Grails Plugin
役立つプラグインばかりでホントウに助かりました。
* [[Spring Security Plugin|http://grails.org/plugin/acegi]]
** ユーザ認証，ユーザ登録はすべてこれがやってくれました
* [[JMS Plugin|http://grails.org/plugin/jms]]と[[ActiveMQ Plugin|http://grails.org/plugin/activemq]]
** これのお陰でSubscriberの登録は２行，メッセージのpublishは1行で完了です。
** もう，笑いが止まりません。
* [[Image Tools Plugin|http://grails.org/plugin/image-tools]]
** アイコン画像のリサイズは，これに任せました。

他にもヒマだったので，[[Feeds|http://grails.org/plugin/feeds]]と[[Searchable|http://grails.org/plugin/searchable]]でRSS発行とメッセージ検索を実装してみました。
おかげさまで，ホントに何も作ってません。:-)</pre>
</div>
<div title="slide.Grailsとは" modifier="ImaiMasanobu" created="200906240625" modified="200906240820" tags="slide" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" changecount="8" creator="ImaiMasanobu">
<pre>!Grailsとは
groovyで作ったrailsみたいなもの（大ざっぱ）。Spring Framework, Hibernate, SitemeshなどのJava資産が使われています。
!!利点
* groovyの表記が簡潔
* オールインワンで環境が揃うため，開発がすぐできる
* 豊富なプラグインによる高い拡張性
!!欠点
* typoによるエラーが多発する
* シンタックスシュガーが豊富過ぎる
* テスト環境が（Railsと比べ）楽しくない
* 地味にJavaの知識が必要。:-P
!＞＞多人数による開発には向かないけど，サッとモノを作るのに非常に向いている</pre>
</div>
<div title="slide.おわりに" modifier="ImaiMasanobu" created="200906240802" modified="200906240814" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" changecount="7" creator="ImaiMasanobu">
<pre>!おわりに
{{{grails stats}}}の結果をどうぞ。
|!Name|!Files|!LOC|
|Controllers|7|673|
|Domain Classes|7|218|
|Services|4|214|
|Tag Libraries|1|67|
|Unit Tests|2|58|
|bgcolor(lightgreen):Totals|bgcolor(lightgreen):21|bgcolor(lightgreen):1,230|
これ以外に画面(GSP)があります。
|!Name|!Files|!LOC|
|GSP|33|1,195|
全部で2,500行くらい。そんなに組んだ気がしないのは，コード生成のおかげでしょう。</pre>
</div>
<div title="slide.はじめに" modifier="ImaiMasanobu" created="200906240554" modified="200906240757" tags="slide" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" changecount="10" creator="ImaiMasanobu">
<pre>!はじめに
!!ホントウの目的
* Grailsを使って，如何にモノを作らずに実装できるのか
* Grailsの生産性の高さを実感したい
* ひとりプロジェクトでもSCM, PMSを効果的に使えるか
!!リハビリ目的
* CSS主体のレイアウトをやってみたい
* AJAX使ってみたい
* TwitterっぽいURLにしてみたい

・・・正直，アプリそのものには関心ありません。すいません。

</pre>
</div>
<div title="slide.オマケ的要素" modifier="ImaiMasanobu" created="200906240745" modified="200906240748" tags="slide" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" changecount="7" creator="ImaiMasanobu">
<pre>!オマケ的要素
* アイコンイメージはDBに格納してある
* GORMではBLOBの扱いが非常に簡単
{{{
class AvatorIcon {
  byte[] largeIcon
  byte[] smallIcon
}
}}}
* それだけじゃツマらないので，[[Ehcache|http://ehcache.sourceforge.net/]]でキャッシュしてみた。
</pre>
</div>
<div title="slide.タイトル" modifier="ImaiMasanobu" created="200906240546" modified="200906240551" tags="slide cover" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" changecount="7" creator="ImaiMasanobu">
<pre>!Twitter Cloneのアーキテクチャ概説

40時間で作るTwitter Clone
（元ネタ：[[Building Twitter with Grails in 40 minutes|http://skillsmatter.com/podcast/java-jee/javawug-bof-44]]）

----
作者：いまいまさのぶ</pre>
</div>
<div title="slide.データモデル" modifier="ImaiMasanobu" created="200906240602" modified="200906240614" tags="slide" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" changecount="7" creator="ImaiMasanobu">
<pre>!データモデルについて
[[きしだモデル|http://d.hatena.ne.jp/nowokay/20090304#1236191758]]を参照しています。ありがとうございました。
あえて，このモデルを無視するのは不経済ですね。
[&gt;img[it4-model.png]]
* Account
** ユーザ情報
* Status
** 発言内容とタイムスタンプを持つ
* Timeline
** ユーザ毎のタイムラインを持つ
** Statusに対するViewの役割
* Reply
** ユーザ宛のメッセージ（Status）へのリンク


</pre>
</div>
<div title="slide.処理モデル" modifier="ImaiMasanobu" created="200906240617" modified="200906240729" tags="slide" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" changecount="9" creator="ImaiMasanobu">
<pre>!処理モデル
ついったーといえば''遅延''です。
大事なことはリクエスト中に，どうでも良い処理は後処理にまわす。
[img[process-model.jpg]]
つまり，非同期処理が''必須''</pre>
</div>
<div title="slide.実装例-DBアクセス" modifier="ImaiMasanobu" created="200906240740" tags="slide" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" changecount="7" creator="ImaiMasanobu">
<pre>!実装例（DBアクセス）
!!タイムラインの取得
{{{
def timelines = Timeline.withCriteria {
  eq 'ownername', username
  maxResults MAX_RESULT
  firstResult(page * MAX_RESULT)
  order 'createDate', 'desc'
}
}}}
!!フォロアーの削除に伴うタイムラインの更新
かっこ悪いけどbulk deleteも出来る
{{{
Timeline.executeUpdate(&quot;&quot;&quot;
  delete Timeline t
  where t.ownername = ?
    and t.statusOwner = (select a from Account a where a.username = ?)
&quot;&quot;&quot;, [follower.owner.username, follower.target.username])
}}}
</pre>
</div>
<div title="slide.実装例-非同期処理" modifier="ImaiMasanobu" created="200906240734" modified="200906240741" tags="slide" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" changecount="7" creator="ImaiMasanobu">
<pre>!実装例（非同期処理）
!!Subscriberの宣言
{{{
class TimelineService {
  static expose = ['jms']
  static destination = &quot;queue.timeline&quot;

  def onMessage = {status -&gt;
    :
}}}
!!指定したSubscribeｒ宛にメッセージをpublishする
{{{
/** つぶやく */
def updateStatus(status) {
  :
  // フォロアーへの通知は非同期で行う
  sendJMSMessage(&quot;queue.timeline&quot;, status)
}
}}}

</pre>
</div>
<div title="slide.拡張性" modifier="ImaiMasanobu" created="200906240756" tags="slide" server.type="file" server.host="www.potto.client.jp/htmlslidymaker" changecount="7" creator="ImaiMasanobu">
<pre>!拡張性
* すでにRSS発行，メッセージの全文検索は実装済み
* RESTful APIに対応するのも難しくはない
* GAE/J用にポートするのは，ちょっと難しい

* 機能追加（たとえば，d の実装など）も難なくできると思う。
* ただし，Railsでいう migration相当の機能を有していないため，運用面で注意が要るのだと推測している。</pre>
</div>
<div title="お気に入りの設定" modifier="ImaiMasanobu" created="200905190213" modified="200906040543" tags="action @IT3 done" gtd.project="UC6.あるユーザをお気に入りに追加する" gtd.context="@IT3" changecount="5" gtd.projectindex="2">
<pre># システムに登録している全ユーザを一覧できる
** ユーザ一覧も20件ずつページングする。
** Twitter本家にならい，ページングのAJAX化は不要。
# ユーザ一覧から特定のユーザのステータスタイムライン画面に遷移できる
# 自分以外のユーザのステータスタイムライン画面で，そのユーザがお気に入り未登録の場合，「登録」ボタンが表示される。
** お気に入り登録済みの場合は，「登録済み」と表示される
** 自分自身のステータスタイムライン画面では，登録ボタンは表示されない
** 未ログインの場合も，同ボタンは表示されない
# お気に入り登録されたユーザは，だれが自分をお気に入りに登録しているかを知ることが出来る。
** これがないと，タイムラインを作れない。

</pre>
</div>
<div title="お気に入り一覧表示" modifier="ImaiMasanobu" created="200905150810" modified="200906040456" tags="action @IT3 extend done" gtd.project="UC6.あるユーザをお気に入りに追加する" gtd.context="@IT3" changecount="4" gtd.projectindex="0">
<pre>ユーザの投稿リスト画面から，そのユーザがお気に入り登録しているユーザを一覧表示する画面に遷移できる。
お気に入りユーザは20人ずつ表示される。

-----
# お気に入り一覧から，ユーザ個別のステータスタイムライン画面に遷移できる。</pre>
</div>
<div title="お気に入り一部表示" modifier="ImaiMasanobu" created="200905150811" modified="200906110635" tags="action @IT5 extend done" gtd.project="UC6.あるユーザをお気に入りに追加する" gtd.context="@IT5" changecount="3" gtd.projectindex="1">
<pre>ユーザの投稿リスト画面に，そのユーザがお気に入り登録しているユーザのうち10人が表示される。

----
# お気に入り登録しているユーザをアイコン（小）で表示する
# アイコンをクリックすると，そのユーザのつぶやき画面に移動する</pre>
</div>
<div title="アイコン登録" modifier="ImaiMasanobu" created="200905150808" modified="200906110726" tags="action @IT5 extend done" gtd.project="UC3.ログインする" gtd.context="@IT5" changecount="4" gtd.projectindex="3">
<pre>ユーザは好きな画像を登録・変更できる。
登録したアイコンは，ユーザの投稿リスト画面に表示されるほか，投稿リスト，個別投稿，タイムラインの書く画面でも投稿と並べて表示される。

----
# イメージファイルのフォーマットは，JPEG/PNG/GIFとする
** ファイルフォーマットは拡張子で判断する
** それ以外のファイルをアップロードしたらエラーとする
# アップロードするファイルの上限は設けない
# アップロードした画像は，元のサイズに関わりなく64x64の大きいアイコンと32x32の小さいアイコンにリサイズする。
# 保持できるアイコンの種類は１種類とする
# アイコンの削除はできない

----
できれば，こんなのもやって
# アイコンはDBに格納する
** アプリケーションのポータビリティを優先したい
# とはいえ都度，BLOBのI/Oを発生させるのも忍びないので，ある程度はメモリ内にキャッシュして，I/Oを減らす
</pre>
</div>
<div title="ステータスタイムラインをAJAX化する" modifier="ImaiMasanobu" created="200905190202" modified="200906010608" tags="action @IT2 experiment done" gtd.project="UC1.あるユーザの投稿リストを見る" gtd.context="@IT2" changecount="3" gtd.projectindex="2">
<pre>----
#ページングを画面遷移しないで行う
#ページングは20件ずつ行う
**ページングは「送り（NEXT)」のみで，「戻り(PREV)」は不要。
#ページングは，その時データベースに格納しているデータを対象に行う。
**ページング，投稿のタイミングによっては表示をロストする投稿がある。</pre>
</div>
<div title="ステータスタイムラインを表示する（基本）" modifier="ImaiMasanobu" created="200905190145" modified="200905290109" tags="action @IT1 done" gtd.project="UC1.あるユーザの投稿リストを見る" gtd.context="@IT1" changecount="10" gtd.projectindex="1">
<pre>----
#ステータスタイムラインはログインしていなくても閲覧できる
#URLは「/''ユーザ名''/status/」とする
**やっぱりTwitterに倣って「/''ユーザ名''/」がいい。
#存在しないユーザのステータスタイムラインは404とする
#タイムラインは投稿の''新しい順''から並べる
**''URLに指定したユーザ名''の投稿だけを表示する
**ログインの有無やログインユーザに関係しない
#投稿内容はHTMLサニタイズして表示する。

↓ページングはイテレーション２でAJAX化と一緒に実装することにした。

#ページングは20件ずつ行う
**ページングは「送り（NEXT)」のみで，「戻り(PREV)」は不要。
#ページングは，その時データベースに格納しているデータを対象に行う。
**ページング，投稿のタイミングによっては表示をロストする投稿がある。</pre>
</div>
<div title="ステータス投稿をAJAX化する" modifier="ImaiMasanobu" created="200905190204" modified="200906010708" tags="action @IT2 experiment done" gtd.project="UC5.投稿する" gtd.context="@IT2" changecount="3" gtd.projectindex="2">
<pre>#画面遷移なしで投稿を行う

できたらやる
#Twitterのように入力文字をリアルタイムにカウントする</pre>
</div>
<div title="ステータス投稿（基本）" modifier="ImaiMasanobu" created="200905190153" modified="200905280457" tags="action @IT1 done" gtd.project="UC5.投稿する" gtd.context="@IT1" changecount="4" gtd.projectindex="1">
<pre>----
#投稿画面のURLは '/', 投稿入力は '/status/update'
#ログインしているときだけ投稿可能
#ユーザIDと投稿時間とともに投稿されたテキストを保存する
**テキストが空や空白でない場合は投稿しない（エラーにもしない）
**投稿文字列から改行は削除する（タブが紛れ込んだ場合も同様）
**投稿文字列の前後の空白はトリムする
#トリム処理を行った後，140文字を越える（x&gt;140)場合は，エラーメッセージを表示して投稿を行わない
**140''文字''であって，140''バイト''ではない
**エラーになった場合，元の投稿文字列を画面に再表示する（投稿が成功した場合は，クリアされる）
#投稿後の画面をリロードしても，二重投稿されないこと</pre>
</div>
<div title="タイムラインを見る（基本）" modifier="ImaiMasanobu" created="200905190156" modified="200906040135" tags="action unfiled @IT3 done" gtd.project="UC4.タイムラインを見る" changecount="3" gtd.projectindex="1" gtd.context="@IT3">
<pre># 自分自身の投稿とお気に入り登録しているユーザの投稿をマージして表示する。
** タイムラインの表示画面と投稿画面は同じ画面にする
# タイムラインもステータスタイムライン同様，20件ずつページングする
** Twitterみたくタイムラインの上限は（とりあえず）設けない

上記を実現するために。
# ユーザはメッセージを投稿したら，自分をお気に入りに登録しているユーザのタイムラインデータを更新する
** これをメッセージ投稿と同じトランザクションで実行すると重くなるので，ユーザ毎に別のトランザクションにする
** 更に言えば，ユーザ毎に非同期処理でこれを行う。
</pre>
</div>
<div title="フォロアーの削除" modifier="ImaiMasanobu" created="200905190217" modified="200906110636" tags="action @IT4 done" gtd.project="UC7.あるユーザをお気に入りから削除する" gtd.context="@IT4" changecount="2" gtd.projectindex="0">
<pre>----
# お気に入りから削除したユーザのつぶやきはタイムラインから見えなくなる
# お気に入りから削除しても，そのユーザからのメッセージは残る</pre>
</div>
<div title="プロフィール" modifier="ImaiMasanobu" created="200905150807" modified="200906110633" tags="action @IT5 extend done" gtd.project="UC3.ログインする" gtd.context="@IT5" changecount="4" gtd.projectindex="1">
<pre>ユーザは自分のプロフィール（テキスト情報）を登録・変更できる。
登録したプロフィールは，ユーザの投稿リスト画面に表示される。

----
# プロフィールにURLが含まれる場合，それをハイパーリンクとして表示する。
# プロフィールにHTMLタグは利用できない。
# プロフィールとして変更可能なユーザ情報は以下の通り
** パスワード
** プロフィール
** アイコン
* '''ユーザ名'''は変更できない</pre>
</div>
<div title="メッセージ機能" modifier="ImaiMasanobu" created="200905150812" modified="200906110721" tags="action @IT4 extend done" gtd.project="UC5.投稿する" gtd.context="@IT4" changecount="4" gtd.projectindex="0">
<pre>投稿したテキストに「@+ユーザID+半角空白」が含まれていた場合，自動的に指定したユーザのタイムラインに追加される。
また，ユーザID部分が，ユーザの投稿リスト画面へのハイパーリンクとして表示される。
----
# メッセージ中に複数の @ があった場合，それぞれのユーザにメッセージを送る
** ユーザIDが重複している場合は，１回だけ送信する
# 存在しないユーザは無視する
** ハイバーリンクを生成してもよい（その場合，リンク先は404扱いとする）
# フォローしていないユーザにもメッセージは届く
# フォローしていないユーザ宛のメッセージはタイムラインに表示されない
** この条件は，ユーザをフォローしたときにも適用される
# メッセージは，遅延なく送付先ユーザに届く。


ユーザAがユーザBをフォローしていて，だれもフォローしていないユーザCがユーザAをフォローした場合。
* ユーザCがフォローした時点で，過去にユーザAがユーザBに送ったメッセージは，ユーザCからは見えない
* その後，ユーザCがユーザBもフォローした場合，
** 過去にユーザBがユーザAに送ったメッセージは，ユーザCからも見えるようになる。
** ユーザCは，ユーザAが今後，ユーザBに送るメッセージも見えるようになる。
** ユーザCがユーザBをフォローした時点で，ユーザAの発言をさかのぼって，ユーザBへの発言が見えるようにはならない
* 上記のように，タイムラインでは，メッセージのロストは認める。
----
[img[it4-post.png]]

メッセージ送信は，つぶやき同様ロストしてはいけないので，つぶやきの登録と同じトランザクション内で処理する。
だから，タイムラインには出てこないけど，メッセージにはでてくる，という事があり得る。</pre>
</div>
<div title="ユーザ登録" modifier="ImaiMasanobu" created="200905150806" modified="200905280616" tags="action @IT1 extend done" gtd.project="UC3.ログインする" gtd.context="@IT1" changecount="5" gtd.projectindex="0">
<pre>ユーザIDとパスワードを登録できる
----
#基本的にacegiプラグインに準拠する
#ユーザ名は，英数字とアンダースコアのみ可とする（いずれも半角）
#ユーザ名の長さは20文字以内とする（1文字は可）
#ユーザ名に使えない文字列が存在する（NGリスト）
**システムが使うURLと被る文字列はユーザ名に使えない
**''システムが使うURL''は順次規定する（例えば，'login'とか）
#パスワードは4文字以上とする（上限は設けない）</pre>
</div>
<div title="ログイン／ログアウトできる" modifier="ImaiMasanobu" created="200905190148" modified="200905280229" tags="action @IT1 done" gtd.project="UC3.ログインする" changecount="3" gtd.context="@IT1" gtd.projectindex="2">
<pre>----
#基本的にacegiプラグインに準拠する
#ログインすると，ログアウトが有効になる
#ログインする前にログアウトしても何も起きない</pre>
</div>
<div title="事前準備プロジェクト" modifier="ImaiMasanobu" created="200905150620" modified="200905150652" tags="project important done" changecount="5" creator="ImaiMasanobu">
<pre>プロジェクトを始める前の準備，いろいろ。


&lt;&lt;gtdAction &quot;実行環境を考える&quot; &quot;unfiled&quot;&gt;&gt;
&lt;&lt;gtdAction &quot;開発環境を考える&quot; &quot;unfiled&quot;&gt;&gt;
&lt;&lt;gtdAction &quot;基本アーキテクチャを考える&quot; &quot;unfiled&quot;&gt;&gt;
&lt;&lt;gtdAction &quot;実行計画を考える&quot; &quot;unfiled&quot;&gt;&gt;
</pre>
</div>
<div title="個別投稿を見る（基本）" modifier="ImaiMasanobu" created="200905190149" modified="200905280229" tags="action @IT1 done" gtd.project="UC2.個別投稿を見る" changecount="3" gtd.projectindex="0" gtd.context="@IT1">
<pre>----
#個別投稿はログインしていなくても閲覧できる
#URLは「/''ユーザ名''/status/''投稿ID''」とする
**''投稿ID''は，それ自体がユニークであるが，あえてユーザ名との組合せを行う。
**投稿IDは存在するが，ユーザ名が異なる組合せの場合は404とする
**存在しない投稿ID，ユーザ名の場合は404とする</pre>
</div>
<div title="基本アーキテクチャを考える" modifier="ImaiMasanobu" created="200905150626" modified="200906040608" tags="action @必ずやる done" gtd.project="事前準備プロジェクト" changecount="3" gtd.projectindex="2" gtd.context="@必ずやる">
<pre>きしださんのアイデアをありがたくちょうだいしたいと思います。
http://d.hatena.ne.jp/nowokay/20090304#1236191758</pre>
</div>
<div title="実行環境を考える" modifier="ImaiMasanobu" created="200905150625" modified="200905150647" tags="action @必ずやる done" gtd.project="事前準備プロジェクト" gtd.context="@必ずやる" changecount="5" gtd.projectindex="0">
<pre>Grails使います。
余力があれば，Grails1.1.1に上げて GAE/J 対応してみるかも知れません。

データベースは，HSQLを使い続けるつもり。</pre>
</div>
<div title="実行計画を考える" modifier="ImaiMasanobu" created="200905150626" modified="200905190224" tags="action @必ずやる done" gtd.project="事前準備プロジェクト" changecount="2" gtd.context="@必ずやる" gtd.projectindex="3">
<pre>action洗い出したし，それに @IT1 から @IT5 まで割り振ったので計画できたことにする。</pre>
</div>
<div title="投稿の削除" modifier="ImaiMasanobu" created="200905150805" modified="200906080711" tags="action @IT4 extend done" gtd.project="UC4.タイムラインを見る" gtd.context="@IT4" changecount="2" gtd.projectindex="0">
<pre>ユーザは自分の投稿を削除できる。削除した投稿を個別表示しようとするとエラーになる。</pre>
</div>
<div title="拡張オプション" modifier="ImaiMasanobu" created="200905150801" modified="200905150812" tags="project" changecount="1">
<pre>必須ではないが，実装可能なら実装する。機能の網羅性として評価される。
</pre>
</div>
<div title="自動リンク" modifier="ImaiMasanobu" created="200905150807" modified="200906110626" tags="action @IT4 extend done" gtd.project="UC1.あるユーザの投稿リストを見る" gtd.context="@IT4" changecount="3" gtd.projectindex="0">
<pre>投稿したテキストにURLが含まれていた場合，ハイパーリンクとして表示される

----
# URLの場合は，target=&quot;_blank&quot;で別ウィンドウに開く
# @username の場合もハイパーリンクとし，そのユーザのつぶやき画面を開く
** 別画面に開かなくてよい。</pre>
</div>
<div title="開発環境を考える" modifier="ImaiMasanobu" created="200905150625" modified="200905150645" tags="action @必ずやる done" gtd.project="事前準備プロジェクト" gtd.context="@必ずやる" changecount="5" gtd.projectindex="1">
<pre>* Grails使うので，プログラミング環境については特に考える必要はない。
** バージョニングには git 使います。
* 要件管理とか，そこらへんの環境について一考しとかないと。
** というわけで, d3 というTiddlyWikiクローンを使ってみてます。

しばらく，この構成で運用してみて，具合が悪ければ都度カイゼンしていこうと思います。</pre>
</div>
</div>
<!--POST-STOREAREA-->
<!--POST-BODY-START-->
<!--POST-BODY-END-->
<script id="jsArea" type="text/javascript">
//<![CDATA[
//
// Please note:
//
// * This code is designed to be readable but for compactness it only includes brief comments. You can see fuller comments
//   in the project Subversion repository at http://svn.tiddlywiki.org/Trunk/core/
//
// * You should never need to modify this source code directly. TiddlyWiki is carefully designed to allow deep customisation
//   without changing the core code. Please consult the development group at http://groups.google.com/group/TiddlyWikiDev
//

//--
//-- Configuration repository
//--

// Miscellaneous options
var config = {
  numRssItems: 20, // Number of items in the RSS feed
  animDuration: 400, // Duration of UI animations in milliseconds
  cascadeFast: 20, // Speed for cascade animations (higher == slower)
  cascadeSlow: 60, // Speed for EasterEgg cascade animations
  cascadeDepth: 5, // Depth of cascade animation
  locale: "en" // W3C language tag
};

// Hashmap of alternative parsers for the wikifier
config.parsers = {};

// Adaptors
config.adaptors = {};
config.defaultAdaptor = null;

// Backstage tasks
config.tasks = {};

// Annotations
config.annotations = {};

// Custom fields to be automatically added to new tiddlers
config.defaultCustomFields = {};

// Messages
config.messages = {
  messageClose: {},
  dates: {},
  tiddlerPopup: {}
};

// Options that can be set in the options panel and/or cookies
config.options = {
  chkRegExpSearch: false,
  chkCaseSensitiveSearch: false,
  chkIncrementalSearch: true,
  chkAnimate: true,
  chkSaveBackups: true,
  chkAutoSave: false,
  chkGenerateAnRssFeed: false,
  chkSaveEmptyTemplate: false,
  chkOpenInNewWindow: true,
  chkToggleLinks: false,
  chkHttpReadOnly: true,
  chkForceMinorUpdate: false,
  chkConfirmDelete: true,
  chkInsertTabs: false,
  chkUsePreForStorage: true, // Whether to use <pre> format for storage
  chkDisplayInstrumentation: false,
  txtBackupFolder: "",
  txtEditorFocus: "text",
  txtMainTab: "tabTimeline",
  txtMoreTab: "moreTabAll",
  txtMaxEditRows: "30",
  txtFileSystemCharSet: "UTF-8",
  txtTheme: ""
};
config.optionsDesc = {};

// Default tiddler templates
var DEFAULT_VIEW_TEMPLATE = 1;
var DEFAULT_EDIT_TEMPLATE = 2;
config.tiddlerTemplates = {
  1: "ViewTemplate",
  2: "EditTemplate"
};

// More messages (rather a legacy layout that should not really be like this)
config.views = {
  wikified: {
    tag: {}
  },
  editor: {
    tagChooser: {}
  }
};

// Backstage tasks
config.backstageTasks = ["save","sync","importTask","tweak","upgrade","plugins"];

// Macros; each has a 'handler' member that is inserted later
config.macros = {
  today: {},
  version: {},
  search: {sizeTextbox: 15},
  tiddler: {},
  tag: {},
  tags: {},
  tagging: {},
  timeline: {},
  allTags: {},
  list: {
    all: {},
    missing: {},
    orphans: {},
    shadowed: {},
    touched: {},
    filter: {}
  },
  closeAll: {},
  permaview: {},
  saveChanges: {},
  slider: {},
  option: {},
  options: {},
  newTiddler: {},
  newJournal: {},
  tabs: {},
  gradient: {},
  message: {},
  view: {defaultView: "text"},
  edit: {},
  tagChooser: {},
  toolbar: {},
  plugins: {},
  refreshDisplay: {},
  importTiddlers: {},
  upgrade: {
    source: "http://www.tiddlywiki.com/upgrade/",
    backupExtension: "pre.core.upgrade"
  },
  sync: {},
  annotations: {}
};

// Commands supported by the toolbar macro
config.commands = {
  closeTiddler: {},
  closeOthers: {},
  editTiddler: {},
  saveTiddler: {hideReadOnly: true},
  cancelTiddler: {},
  deleteTiddler: {hideReadOnly: true},
  permalink: {},
  references: {type: "popup"},
  jump: {type: "popup"},
  syncing: {type: "popup"},
  fields: {type: "popup"}
};

// Browser detection... In a very few places, there's nothing else for it but to know what browser we're using.
config.userAgent = navigator.userAgent.toLowerCase();
config.browser = {
  isIE: config.userAgent.indexOf("msie") != -1 && config.userAgent.indexOf("opera") == -1,
  isGecko: config.userAgent.indexOf("gecko") != -1,
  ieVersion: /MSIE (\d.\d)/i.exec(config.userAgent), // config.browser.ieVersion[1], if it exists, will be the IE version string, eg "6.0"
  isSafari: config.userAgent.indexOf("applewebkit") != -1,
  isBadSafari: !((new RegExp("[\u0150\u0170]", "g")).test("\u0150")),
  firefoxDate: /gecko\/(\d{8})/i.exec(config.userAgent), // config.browser.firefoxDate[1], if it exists, will be Firefox release date as "YYYYMMDD"
  isOpera: config.userAgent.indexOf("opera") != -1,
  isLinux: config.userAgent.indexOf("linux") != -1,
  isUnix: config.userAgent.indexOf("x11") != -1,
  isMac: config.userAgent.indexOf("mac") != -1,
  isWindows: config.userAgent.indexOf("win") != -1
};

// Basic regular expressions
config.textPrimitives = {
  upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
  lowerLetter: "[a-z0-9_\\-\u00df-\u00ff\u0151\u0171]",
  anyLetter:   "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
  anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]"
};
if (config.browser.isBadSafari) {
  config.textPrimitives = {
    upperLetter: "[A-Z\u00c0-\u00de]",
    lowerLetter: "[a-z0-9_\\-\u00df-\u00ff]",
    anyLetter:   "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff]",
    anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff]"
  };
}
config.textPrimitives.sliceSeparator = "::";
config.textPrimitives.sectionSeparator = "##";
config.textPrimitives.urlPattern = "(?:file|http|https|mailto|ftp|irc|news|data):[^\\s'\"]+(?:/|\\b)";
config.textPrimitives.unWikiLink = "~";
config.textPrimitives.wikiLink = "(?:(?:" + config.textPrimitives.upperLetter + "+" +
                                 config.textPrimitives.lowerLetter + "+" +
                                 config.textPrimitives.upperLetter +
                                 config.textPrimitives.anyLetter + "*)|(?:" +
                                 config.textPrimitives.upperLetter + "{2,}" +
                                 config.textPrimitives.lowerLetter + "+))";

config.textPrimitives.cssLookahead = "(?:(" + config.textPrimitives.anyLetter + "+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:(" + config.textPrimitives.anyLetter + "+):([^;\\|\\n]+);)";
config.textPrimitives.cssLookaheadRegExp = new RegExp(config.textPrimitives.cssLookahead, "mg");

config.textPrimitives.brackettedLink = "\\[\\[([^\\]]+)\\]\\]";
config.textPrimitives.titledBrackettedLink = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]";
config.textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" + config.textPrimitives.titledBrackettedLink + ")|(?:" +
                                                           config.textPrimitives.brackettedLink + ")|(?:" +
                                                           config.textPrimitives.urlPattern + ")", "mg");
config.textPrimitives.tiddlerAnyLinkRegExp = new RegExp("(" + config.textPrimitives.wikiLink + ")|(?:" +
                                                        config.textPrimitives.titledBrackettedLink + ")|(?:" +
                                                        config.textPrimitives.brackettedLink + ")|(?:" +
                                                        config.textPrimitives.urlPattern + ")", "mg");

config.glyphs = {
  browsers: [
    function() {return config.browser.isIE;},
    function() {return true;}
  ],
  currBrowser: null,
  codes: {
    downTriangle: ["\u25BC","\u25BE"],
    downArrow: ["\u2193","\u2193"],
    bentArrowLeft: ["\u2190","\u21A9"],
    bentArrowRight: ["\u2192","\u21AA"]
  }
};

//--
//-- Shadow tiddlers
//--

config.shadowTiddlers = {
  StyleSheet: "",
  MarkupPreHead: "",
  MarkupPostHead: "",
  MarkupPreBody: "",
  MarkupPostBody: "",
  TabTimeline: '<<timeline>>',
  TabAll: '<<list all>>',
  TabTags: '<<allTags excludeLists>>',
  TabMoreMissing: '<<list missing>>',
  TabMoreOrphans: '<<list orphans>>',
  TabMoreShadowed: '<<list shadowed>>',
  AdvancedOptions: '<<options>>',
  PluginManager: '<<plugins>>',
  ToolbarCommands: '|~ViewToolbar|closeTiddler closeOthers +editTiddler > fields syncing permalink references jump|\n|~EditToolbar|+saveTiddler -cancelTiddler deleteTiddler|'
};

//--
//-- Translateable strings
//--

// Strings in "double quotes" should be translated; strings in 'single quotes' should be left alone

merge(config.options, {
  txtUserName: "YourName"});

merge(config.tasks, {
  save: {text: "save", tooltip: "Save your changes to this TiddlyWiki", action: saveChanges},
  sync: {text: "sync", tooltip: "Synchronise changes with other TiddlyWiki files and servers", content: '<<sync>>'},
  importTask: {text: "import", tooltip: "Import tiddlers and plugins from other TiddlyWiki files and servers", content: '<<importTiddlers>>'},
  tweak: {text: "tweak", tooltip: "Tweak the appearance and behaviour of TiddlyWiki", content: '<<options>>'},
  upgrade: {text: "upgrade", tooltip: "Upgrade TiddlyWiki core code", content: '<<upgrade>>'},
  plugins: {text: "plugins", tooltip: "Manage installed plugins", content: '<<plugins>>'}
});

// Options that can be set in the options panel and/or cookies
merge(config.optionsDesc, {
  txtUserName: "Username for signing your edits",
  chkRegExpSearch: "Enable regular expressions for searches",
  chkCaseSensitiveSearch: "Case-sensitive searching",
  chkIncrementalSearch: "Incremental key-by-key searching",
  chkAnimate: "Enable animations",
  chkSaveBackups: "Keep backup file when saving changes",
  chkAutoSave: "Automatically save changes",
  chkGenerateAnRssFeed: "Generate an RSS feed when saving changes",
  chkSaveEmptyTemplate: "Generate an empty template when saving changes",
  chkOpenInNewWindow: "Open external links in a new window",
  chkToggleLinks: "Clicking on links to open tiddlers causes them to close",
  chkHttpReadOnly: "Hide editing features when viewed over HTTP",
  chkForceMinorUpdate: "Don't update modifier username and date when editing tiddlers",
  chkConfirmDelete: "Require confirmation before deleting tiddlers",
  chkInsertTabs: "Use the tab key to insert tab characters instead of moving between fields",
  txtBackupFolder: "Name of folder to use for backups",
  txtMaxEditRows: "Maximum number of rows in edit boxes",
  txtFileSystemCharSet: "Default character set for saving changes (Firefox/Mozilla only)"});

merge(config.messages, {
  customConfigError: "Problems were encountered loading plugins. See PluginManager for details",
  pluginError: "Error: %0",
  pluginDisabled: "Not executed because disabled via 'systemConfigDisable' tag",
  pluginForced: "Executed because forced via 'systemConfigForce' tag",
  pluginVersionError: "Not executed because this plugin needs a newer version of TiddlyWiki",
  nothingSelected: "Nothing is selected. You must select one or more items first",
  savedSnapshotError: "It appears that this TiddlyWiki has been incorrectly saved. Please see http://www.tiddlywiki.com/#DownloadSoftware for details",
  subtitleUnknown: "(unknown)",
  undefinedTiddlerToolTip: "The tiddler '%0' doesn't yet exist",
  shadowedTiddlerToolTip: "The tiddler '%0' doesn't yet exist, but has a pre-defined shadow value",
  tiddlerLinkTooltip: "%0 - %1, %2",
  externalLinkTooltip: "External link to %0",
  noTags: "There are no tagged tiddlers",
  notFileUrlError: "You need to save this TiddlyWiki to a file before you can save changes",
  cantSaveError: "It's not possible to save changes. Possible reasons include:\n- your browser doesn't support saving (Firefox, Internet Explorer, Safari and Opera all work if properly configured)\n- the pathname to your TiddlyWiki file contains illegal characters\n- the TiddlyWiki HTML file has been moved or renamed",
  invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
  backupSaved: "Backup saved",
  backupFailed: "Failed to save backup file",
  rssSaved: "RSS feed saved",
  rssFailed: "Failed to save RSS feed file",
  emptySaved: "Empty template saved",
  emptyFailed: "Failed to save empty template file",
  mainSaved: "Main TiddlyWiki file saved",
  mainFailed: "Failed to save main TiddlyWiki file. Your changes have not been saved",
  macroError: "Error in macro <<\%0>>",
  macroErrorDetails: "Error while executing macro <<\%0>>:\n%1",
  missingMacro: "No such macro",
  overwriteWarning: "A tiddler named '%0' already exists. Choose OK to overwrite it",
  unsavedChangesWarning: "WARNING! There are unsaved changes in TiddlyWiki\n\nChoose OK to save\nChoose CANCEL to discard",
  confirmExit: "--------------------------------\n\nThere are unsaved changes in TiddlyWiki. If you continue you will lose those changes\n\n--------------------------------",
  saveInstructions: "SaveChanges",
  unsupportedTWFormat: "Unsupported TiddlyWiki format '%0'",
  tiddlerSaveError: "Error when saving tiddler '%0'",
  tiddlerLoadError: "Error when loading tiddler '%0'",
  wrongSaveFormat: "Cannot save with storage format '%0'. Using standard format for save.",
  invalidFieldName: "Invalid field name %0",
  fieldCannotBeChanged: "Field '%0' cannot be changed",
  loadingMissingTiddler: "Attempting to retrieve the tiddler '%0' from the '%1' server at:\n\n'%2' in the workspace '%3'",
  upgradeDone: "The upgrade to version %0 is now complete\n\nClick 'OK' to reload the newly upgraded TiddlyWiki"});

merge(config.messages.messageClose, {
  text: "close",
  tooltip: "close this message area"});

config.messages.backstage = {
  open: {text: "backstage", tooltip: "Open the backstage area to perform authoring and editing tasks"},
  close: {text: "close", tooltip: "Close the backstage area"},
  prompt: "backstage: ",
  decal: {
    edit: {text: "edit", tooltip: "Edit the tiddler '%0'"}
  }
};

config.messages.listView = {
  tiddlerTooltip: "Click for the full text of this tiddler",
  previewUnavailable: "(preview not available)"
};

config.messages.dates.months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"];
config.messages.dates.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
config.messages.dates.shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
config.messages.dates.shortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
// suffixes for dates, eg "1st","2nd","3rd"..."30th","31st"
config.messages.dates.daySuffixes = ["st","nd","rd","th","th","th","th","th","th","th",
  "th","th","th","th","th","th","th","th","th","th",
  "st","nd","rd","th","th","th","th","th","th","th",
  "st"];
config.messages.dates.am = "am";
config.messages.dates.pm = "pm";

merge(config.messages.tiddlerPopup, {
});

merge(config.views.wikified.tag, {
  labelNoTags: "no tags",
  labelTags: "tags: ",
  openTag: "Open tag '%0'",
  tooltip: "Show tiddlers tagged with '%0'",
  openAllText: "Open all",
  openAllTooltip: "Open all of these tiddlers",
  popupNone: "No other tiddlers tagged with '%0'"});

merge(config.views.wikified, {
  defaultText: "The tiddler '%0' doesn't yet exist. Double-click to create it",
  defaultModifier: "(missing)",
  shadowModifier: "(built-in shadow tiddler)",
  dateFormat: "DD MMM YYYY",
  createdPrompt: "created"});

merge(config.views.editor, {
  tagPrompt: "Type tags separated with spaces, [[use double square brackets]] if necessary, or add existing",
  defaultText: "Type the text for '%0'"});

merge(config.views.editor.tagChooser, {
  text: "tags",
  tooltip: "Choose existing tags to add to this tiddler",
  popupNone: "There are no tags defined",
  tagTooltip: "Add the tag '%0'"});

merge(config.messages, {
  sizeTemplates:
          [
            {
              unit: 1024 * 1024 * 1024,
              template: "%0\u00a0GB"
            },
            {
              unit: 1024 * 1024,
              template: "%0\u00a0MB"
            },
            {
              unit: 1024,
              template: "%0\u00a0KB"
            },
            {
              unit: 1,
              template: "%0\u00a0B"
            }
          ]});

merge(config.macros.search, {
  label: "search",
  prompt: "Search this TiddlyWiki",
  accessKey: "F",
  successMsg: "%0 tiddlers found matching %1",
  failureMsg: "No tiddlers found matching %0"});

merge(config.macros.tagging, {
  label: "tagging: ",
  labelNotTag: "not tagging",
  tooltip: "List of tiddlers tagged with '%0'"});

merge(config.macros.timeline, {
  dateFormat: "DD MMM YYYY"});

merge(config.macros.allTags, {
  tooltip: "Show tiddlers tagged with '%0'",
  noTags: "There are no tagged tiddlers"});

config.macros.list.all.prompt = "All tiddlers in alphabetical order";
config.macros.list.missing.prompt = "Tiddlers that have links to them but are not defined";
config.macros.list.orphans.prompt = "Tiddlers that are not linked to from any other tiddlers";
config.macros.list.shadowed.prompt = "Tiddlers shadowed with default contents";
config.macros.list.touched.prompt = "Tiddlers that have been modified locally";

merge(config.macros.closeAll, {
  label: "close all",
  prompt: "Close all displayed tiddlers (except any that are being edited)"});

merge(config.macros.permaview, {
  label: "permaview",
  prompt: "Link to an URL that retrieves all the currently displayed tiddlers"});

merge(config.macros.saveChanges, {
  label: "save changes",
  prompt: "Save all tiddlers to create a new TiddlyWiki",
  accessKey: "S"});

merge(config.macros.newTiddler, {
  label: "new tiddler",
  prompt: "Create a new tiddler",
  title: "New Tiddler",
  accessKey: "N"});

merge(config.macros.newJournal, {
  label: "new journal",
  prompt: "Create a new tiddler from the current date and time",
  accessKey: "J"});

merge(config.macros.options, {
  wizardTitle: "Tweak advanced options",
  step1Title: "These options are saved in cookies in your browser",
  step1Html: "<input type='hidden' name='markList'></input><br><input type='checkbox' checked='false' name='chkUnknown'>Show unknown options</input>",
  unknownDescription: "//(unknown)//",
  listViewTemplate: {
    columns: [
      {
        name: 'Option',
        field: 'option',
        title: "Option",
        type: 'String'
      },
      {
        name: 'Description',
        field: 'description',
        title: "Description",
        type: 'WikiText'
      },
      {
        name: 'Name',
        field: 'name',
        title: "Name",
        type: 'String'
      }
    ],
    rowClasses: [
      {
        className: 'lowlight',
        field: 'lowlight'
      }
    ]}
});

merge(config.macros.plugins, {
  wizardTitle: "Manage plugins",
  step1Title: "Currently loaded plugins",
  step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
  skippedText: "(This plugin has not been executed because it was added since startup)",
  noPluginText: "There are no plugins installed",
  confirmDeleteText: "Are you sure you want to delete these plugins:\n\n%0",
  removeLabel: "remove systemConfig tag",
  removePrompt: "Remove systemConfig tag",
  deleteLabel: "delete",
  deletePrompt: "Delete these tiddlers forever",
  listViewTemplate: {
    columns: [
      {
        name: 'Selected',
        field: 'Selected',
        rowName: 'title',
        type: 'Selector'
      },
      {
        name: 'Tiddler',
        field: 'tiddler',
        title: "Tiddler",
        type: 'Tiddler'
      },
      {
        name: 'Size',
        field: 'size',
        tiddlerLink: 'size',
        title: "Size",
        type: 'Size'
      },
      {
        name: 'Forced',
        field: 'forced',
        title: "Forced",
        tag: 'systemConfigForce',
        type: 'TagCheckbox'
      },
      {
        name: 'Disabled',
        field: 'disabled',
        title: "Disabled",
        tag: 'systemConfigDisable',
        type: 'TagCheckbox'
      },
      {
        name: 'Executed',
        field: 'executed',
        title: "Loaded",
        type: 'Boolean',
        trueText: "Yes",
        falseText: "No"
      },
      {
        name: 'Startup Time',
        field: 'startupTime',
        title: "Startup Time",
        type: 'String'
      },
      {
        name: 'Error',
        field: 'error',
        title: "Status",
        type: 'Boolean',
        trueText: "Error",
        falseText: "OK"
      },
      {
        name: 'Log',
        field: 'log',
        title: "Log",
        type: 'StringList'
      }
    ],
    rowClasses: [
      {
        className: 'error',
        field: 'error'
      },
      {
        className: 'warning',
        field: 'warning'
      }
    ]}
});

merge(config.macros.toolbar, {
  moreLabel: "more",
  morePrompt: "Reveal further commands"
});

merge(config.macros.refreshDisplay, {
  label: "refresh",
  prompt: "Redraw the entire TiddlyWiki display"
});

merge(config.macros.importTiddlers, {
  readOnlyWarning: "You cannot import into a read-only TiddlyWiki file. Try opening it from a file:// URL",
  wizardTitle: "Import tiddlers from another file or server",
  step1Title: "Step 1: Locate the server or TiddlyWiki file",
  step1Html: "Specify the type of the server: <select name='selTypes'><option value=''>Choose...</option></select><br>Enter the URL or pathname here: <input type='text' size=50 name='txtPath'><br>...or browse for a file: <input type='file' size=50 name='txtBrowse'><br><hr>...or select a pre-defined feed: <select name='selFeeds'><option value=''>Choose...</option></select>",
  openLabel: "open",
  openPrompt: "Open the connection to this file or server",
  openError: "There were problems fetching the tiddlywiki file",
  statusOpenHost: "Opening the host",
  statusGetWorkspaceList: "Getting the list of available workspaces",
  step2Title: "Step 2: Choose the workspace",
  step2Html: "Enter a workspace name: <input type='text' size=50 name='txtWorkspace'><br>...or select a workspace: <select name='selWorkspace'><option value=''>Choose...</option></select>",
  cancelLabel: "cancel",
  cancelPrompt: "Cancel this import",
  statusOpenWorkspace: "Opening the workspace",
  statusGetTiddlerList: "Getting the list of available tiddlers",
  errorGettingTiddlerList: "Error getting list of tiddlers, click Cancel to try again",
  step3Title: "Step 3: Choose the tiddlers to import",
  step3Html: "<input type='hidden' name='markList'></input><br><input type='checkbox' checked='true' name='chkSync'>Keep these tiddlers linked to this server so that you can synchronise subsequent changes</input><br><input type='checkbox' name='chkSave'>Save the details of this server in a 'systemServer' tiddler called:</input> <input type='text' size=25 name='txtSaveTiddler'>",
  importLabel: "import",
  importPrompt: "Import these tiddlers",
  confirmOverwriteText: "Are you sure you want to overwrite these tiddlers:\n\n%0",
  step4Title: "Step 4: Importing %0 tiddler(s)",
  step4Html: "<input type='hidden' name='markReport'></input>", // DO NOT TRANSLATE
  doneLabel: "done",
  donePrompt: "Close this wizard",
  statusDoingImport: "Importing tiddlers",
  statusDoneImport: "All tiddlers imported",
  systemServerNamePattern: "%2 on %1",
  systemServerNamePatternNoWorkspace: "%1",
  confirmOverwriteSaveTiddler: "The tiddler '%0' already exists. Click 'OK' to overwrite it with the details of this server, or 'Cancel' to leave it unchanged",
  serverSaveTemplate: "|''Type:''|%0|\n|''URL:''|%1|\n|''Workspace:''|%2|\n\nThis tiddler was automatically created to record the details of this server",
  serverSaveModifier: "(System)",
  listViewTemplate: {
    columns: [
      {
        name: 'Selected',
        field: 'Selected',
        rowName: 'title',
        type: 'Selector'
      },
      {
        name: 'Tiddler',
        field: 'tiddler',
        title: "Tiddler",
        type: 'Tiddler'
      },
      {
        name: 'Size',
        field: 'size',
        tiddlerLink: 'size',
        title: "Size",
        type: 'Size'
      },
      {
        name: 'Tags',
        field: 'tags',
        title: "Tags",
        type: 'Tags'
      }
    ],
    rowClasses: [
    ]}
});

merge(config.macros.upgrade, {
  wizardTitle: "Upgrade TiddlyWiki core code",
  step1Title: "Update or repair this TiddlyWiki to the latest release",
  step1Html: "You are about to upgrade to the latest release of the TiddlyWiki core code (from <a href='%0' class='externalLink' target='_blank'>%1</a>). Your content will be preserved across the upgrade.<br><br>Note that core upgrades have been known to interfere with older plugins. If you run into problems with the upgraded file, see <a href='http://www.tiddlywiki.org/wiki/CoreUpgrades' class='externalLink' target='_blank'>http://www.tiddlywiki.org/wiki/CoreUpgrades</a>",
  errorCantUpgrade: "Unable to upgrade this TiddlyWiki. You can only perform upgrades on TiddlyWiki files stored locally",
  errorNotSaved: "You must save changes before you can perform an upgrade",
  step2Title: "Confirm the upgrade details",
  step2Html_downgrade: "You are about to downgrade to TiddlyWiki version %0 from %1.<br><br>Downgrading to an earlier version of the core code is not recommended",
  step2Html_restore: "This TiddlyWiki appears to be already using the latest version of the core code (%0).<br><br>You can continue to upgrade anyway to ensure that the core code hasn't been corrupted or damaged",
  step2Html_upgrade: "You are about to upgrade to TiddlyWiki version %0 from %1",
  upgradeLabel: "upgrade",
  upgradePrompt: "Prepare for the upgrade process",
  statusPreparingBackup: "Preparing backup",
  statusSavingBackup: "Saving backup file",
  errorSavingBackup: "There was a problem saving the backup file",
  statusLoadingCore: "Loading core code",
  errorLoadingCore: "Error loading the core code",
  errorCoreFormat: "Error with the new core code",
  statusSavingCore: "Saving the new core code",
  statusReloadingCore: "Reloading the new core code",
  startLabel: "start",
  startPrompt: "Start the upgrade process",
  cancelLabel: "cancel",
  cancelPrompt: "Cancel the upgrade process",
  step3Title: "Upgrade cancelled",
  step3Html: "You have cancelled the upgrade process"
});

merge(config.macros.sync, {
  listViewTemplate: {
    columns: [
      {
        name: 'Selected',
        field: 'selected',
        rowName: 'title',
        type: 'Selector'
      },
      {
        name: 'Tiddler',
        field: 'tiddler',
        title: "Tiddler",
        type: 'Tiddler'
      },
      {
        name: 'Server Type',
        field: 'serverType',
        title: "Server type",
        type: 'String'
      },
      {
        name: 'Server Host',
        field: 'serverHost',
        title: "Server host",
        type: 'String'
      },
      {
        name: 'Server Workspace',
        field: 'serverWorkspace',
        title: "Server workspace",
        type: 'String'
      },
      {
        name: 'Status',
        field: 'status',
        title: "Synchronisation status",
        type: 'String'
      },
      {
        name: 'Server URL',
        field: 'serverUrl',
        title: "Server URL",
        text: "View",
        type: 'Link'
      }
    ],
    rowClasses: [
    ],
    buttons: [
      {
        caption: "Sync these tiddlers",
        name: 'sync'
      }
    ]},
  wizardTitle: "Synchronize with external servers and files",
  step1Title: "Choose the tiddlers you want to synchronize",
  step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
  syncLabel: "sync",
  syncPrompt: "Sync these tiddlers",
  hasChanged: "Changed while unplugged",
  hasNotChanged: "Unchanged while unplugged",
  syncStatusList: {
    none: {text: "...", display:null, className:'notChanged'},
    changedServer: {text: "Changed on server", display:null, className:'changedServer'},
    changedLocally: {text: "Changed while unplugged", display:null, className:'changedLocally'},
    changedBoth: {text: "Changed while unplugged and on server", display:null, className:'changedBoth'},
    notFound: {text: "Not found on server", display:null, className:'notFound'},
    putToServer: {text: "Saved update on server", display:null, className:'putToServer'},
    gotFromServer: {text: "Retrieved update from server", display:null, className:'gotFromServer'}
  }
});

merge(config.macros.annotations, {
});

merge(config.commands.closeTiddler, {
  text: "close",
  tooltip: "Close this tiddler"});

merge(config.commands.closeOthers, {
  text: "close others",
  tooltip: "Close all other tiddlers"});

merge(config.commands.editTiddler, {
  text: "edit",
  tooltip: "Edit this tiddler",
  readOnlyText: "view",
  readOnlyTooltip: "View the source of this tiddler"});

merge(config.commands.saveTiddler, {
  text: "done",
  tooltip: "Save changes to this tiddler"});

merge(config.commands.cancelTiddler, {
  text: "cancel",
  tooltip: "Undo changes to this tiddler",
  warning: "Are you sure you want to abandon your changes to '%0'?",
  readOnlyText: "done",
  readOnlyTooltip: "View this tiddler normally"});

merge(config.commands.deleteTiddler, {
  text: "delete",
  tooltip: "Delete this tiddler",
  warning: "Are you sure you want to delete '%0'?"});

merge(config.commands.permalink, {
  text: "permalink",
  tooltip: "Permalink for this tiddler"});

merge(config.commands.references, {
  text: "references",
  tooltip: "Show tiddlers that link to this one",
  popupNone: "No references"});

merge(config.commands.jump, {
  text: "jump",
  tooltip: "Jump to another open tiddler"});

merge(config.commands.syncing, {
  text: "syncing",
  tooltip: "Control synchronisation of this tiddler with a server or external file",
  currentlySyncing: "<div>Currently syncing via <span class='popupHighlight'>'%0'</span> to:</" + "div><div>host: <span class='popupHighlight'>%1</span></" + "div><div>workspace: <span class='popupHighlight'>%2</span></" + "div>", // Note escaping of closing <div> tag
  notCurrentlySyncing: "Not currently syncing",
  captionUnSync: "Stop synchronising this tiddler",
  chooseServer: "Synchronise this tiddler with another server:",
  currServerMarker: "\u25cf ",
  notCurrServerMarker: "  "});

merge(config.commands.fields, {
  text: "fields",
  tooltip: "Show the extended fields of this tiddler",
  emptyText: "There are no extended fields for this tiddler",
  listViewTemplate: {
    columns: [
      {
        name: 'Field',
        field: 'field',
        title: "Field",
        type: 'String'
      },
      {
        name: 'Value',
        field: 'value',
        title: "Value",
        type: 'String'
      }
    ],
    rowClasses: [
    ],
    buttons: [
    ]}});

merge(config.shadowTiddlers, {
  DefaultTiddlers: "[[GettingStarted]]",
  MainMenu: "[[GettingStarted]]",
  SiteTitle: "My TiddlyWiki",
  SiteSubtitle: "a reusable non-linear personal web notebook",
  SiteUrl: "http://www.tiddlywiki.com/",
  SideBarOptions: '<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal "DD MMM YYYY" "journal">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options \u00bb" "Change TiddlyWiki advanced options">>',
  SideBarTabs: '<<tabs txtMainTab "Timeline" "Timeline" TabTimeline "All" "All tiddlers" TabAll "Tags" "All tags" TabTags "More" "More lists" TabMore>>',
  TabMore: '<<tabs txtMoreTab "Missing" "Missing tiddlers" TabMoreMissing "Orphans" "Orphaned tiddlers" TabMoreOrphans "Shadowed" "Shadowed tiddlers" TabMoreShadowed>>'
});

merge(config.annotations, {
  AdvancedOptions: "This shadow tiddler provides access to several advanced options",
  ColorPalette: "These values in this shadow tiddler determine the colour scheme of the ~TiddlyWiki user interface",
  DefaultTiddlers: "The tiddlers listed in this shadow tiddler will be automatically displayed when ~TiddlyWiki starts up",
  EditTemplate: "The HTML template in this shadow tiddler determines how tiddlers look while they are being edited",
  GettingStarted: "This shadow tiddler provides basic usage instructions",
  ImportTiddlers: "This shadow tiddler provides access to importing tiddlers",
  MainMenu: "This shadow tiddler is used as the contents of the main menu in the left-hand column of the screen",
  MarkupPreHead: "This tiddler is inserted at the top of the <head> section of the TiddlyWiki HTML file",
  MarkupPostHead: "This tiddler is inserted at the bottom of the <head> section of the TiddlyWiki HTML file",
  MarkupPreBody: "This tiddler is inserted at the top of the <body> section of the TiddlyWiki HTML file",
  MarkupPostBody: "This tiddler is inserted at the end of the <body> section of the TiddlyWiki HTML file immediately after the script block",
  OptionsPanel: "This shadow tiddler is used as the contents of the options panel slider in the right-hand sidebar",
  PageTemplate: "The HTML template in this shadow tiddler determines the overall ~TiddlyWiki layout",
  PluginManager: "This shadow tiddler provides access to the plugin manager",
  SideBarOptions: "This shadow tiddler is used as the contents of the option panel in the right-hand sidebar",
  SideBarTabs: "This shadow tiddler is used as the contents of the tabs panel in the right-hand sidebar",
  SiteSubtitle: "This shadow tiddler is used as the second part of the page title",
  SiteTitle: "This shadow tiddler is used as the first part of the page title",
  SiteUrl: "This shadow tiddler should be set to the full target URL for publication",
  StyleSheetColors: "This shadow tiddler contains CSS definitions related to the color of page elements. ''DO NOT EDIT THIS TIDDLER'', instead make your changes in the StyleSheet shadow tiddler",
  StyleSheet: "This tiddler can contain custom CSS definitions",
  StyleSheetLayout: "This shadow tiddler contains CSS definitions related to the layout of page elements. ''DO NOT EDIT THIS TIDDLER'', instead make your changes in the StyleSheet shadow tiddler",
  StyleSheetLocale: "This shadow tiddler contains CSS definitions related to the translation locale",
  StyleSheetPrint: "This shadow tiddler contains CSS definitions for printing",
  TabAll: "This shadow tiddler contains the contents of the 'All' tab in the right-hand sidebar",
  TabMore: "This shadow tiddler contains the contents of the 'More' tab in the right-hand sidebar",
  TabMoreMissing: "This shadow tiddler contains the contents of the 'Missing' tab in the right-hand sidebar",
  TabMoreOrphans: "This shadow tiddler contains the contents of the 'Orphans' tab in the right-hand sidebar",
  TabMoreShadowed: "This shadow tiddler contains the contents of the 'Shadowed' tab in the right-hand sidebar",
  TabTags: "This shadow tiddler contains the contents of the 'Tags' tab in the right-hand sidebar",
  TabTimeline: "This shadow tiddler contains the contents of the 'Timeline' tab in the right-hand sidebar",
  ToolbarCommands: "This shadow tiddler determines which commands are shown in tiddler toolbars",
  ViewTemplate: "The HTML template in this shadow tiddler determines how tiddlers look"
});

//--
//-- Main
//--

var params = null; // Command line parameters
var store = null; // TiddlyWiki storage
var story = null; // Main story
var formatter = null; // Default formatters for the wikifier
var anim = typeof Animator == "function" ? new Animator() : null; // Animation engine
var readOnly = false; // Whether we're in readonly mode
var highlightHack = null; // Embarrassing hack department...
var hadConfirmExit = false; // Don't warn more than once
var safeMode = false; // Disable all plugins and cookies
var showBackstage; // Whether to include the backstage area
var installedPlugins = []; // Information filled in when plugins are executed
var startingUp = false; // Whether we're in the process of starting up
var pluginInfo,tiddler; // Used to pass information to plugins in loadPlugins()

// Whether to use the JavaSaver applet
var useJavaSaver = (config.browser.isSafari || config.browser.isOpera) && (document.location.toString().substr(0, 4) != "http");

// Starting up
function main()
{
  var t10,t9,t8,t7,t6,t5,t4,t3,t2,t1,t0 = new Date();
  startingUp = true;
  window.onbeforeunload = function(e) {if (window.confirmExit) return confirmExit();};
  params = getParameters();
  if (params)
    params = params.parseParams("open", null, false);
  store = new TiddlyWiki();
  invokeParamifier(params, "oninit");
  story = new Story("tiddlerDisplay", "tiddler");
  addEvent(document, "click", Popup.onDocumentClick);
  saveTest();
  loadOptionsCookie();
  for (var s = 0; s < config.notifyTiddlers.length; s++)
    store.addNotification(config.notifyTiddlers[s].name, config.notifyTiddlers[s].notify);
  t1 = new Date();
  loadShadowTiddlers();
  t2 = new Date();
  store.loadFromDiv("storeArea", "store", true);
  t3 = new Date();
  invokeParamifier(params, "onload");
  t4 = new Date();
  readOnly = (window.location.protocol == "file:") ? false : config.options.chkHttpReadOnly;
  var pluginProblem = loadPlugins();
  t5 = new Date();
  formatter = new Formatter(config.formatters);
  invokeParamifier(params, "onconfig");
  story.switchTheme(config.options.txtTheme);
  showBackstage = !readOnly;
  t6 = new Date();
  store.notifyAll();
  t7 = new Date();
  restart();
  refreshDisplay();
  t8 = new Date();
  if (pluginProblem) {
    story.displayTiddler(null, "PluginManager");
    displayMessage(config.messages.customConfigError);
  }
  for (var m in config.macros) {
    if (config.macros[m].init)
      config.macros[m].init();
  }
  t9 = new Date();
  if (showBackstage)
    backstage.init();
  t10 = new Date();
  if (config.options.chkDisplayInstrumentation) {
    displayMessage("LoadShadows " + (t2 - t1) + " ms");
    displayMessage("LoadFromDiv " + (t3 - t2) + " ms");
    displayMessage("LoadPlugins " + (t5 - t4) + " ms");
    displayMessage("Notify " + (t7 - t6) + " ms");
    displayMessage("Restart " + (t8 - t7) + " ms");
    displayMessage("Macro init " + (t9 - t8) + " ms");
    displayMessage("Total: " + (t10 - t0) + " ms");
  }
  startingUp = false;
}

// Restarting
function restart()
{
  invokeParamifier(params, "onstart");
  if (story.isEmpty()) {
    story.displayDefaultTiddlers();
  }
  window.scrollTo(0, 0);
}

function saveTest()
{
  var s = document.getElementById("saveTest");
  if (s.hasChildNodes())
    alert(config.messages.savedSnapshotError);
  s.appendChild(document.createTextNode("savetest"));
}

function loadShadowTiddlers()
{
  var shadows = new TiddlyWiki();
  shadows.loadFromDiv("shadowArea", "shadows", true);
  shadows.forEachTiddler(function(title, tiddler) {config.shadowTiddlers[title] = tiddler.text;});
  delete shadows;
}

function loadPlugins()
{
  if (safeMode)
    return false;
  var tiddlers = store.getTaggedTiddlers("systemConfig");
  var toLoad = [];
  var nLoaded = 0;
  var map = {};
  var nPlugins = tiddlers.length;
  installedPlugins = [];
  for (var i = 0; i < nPlugins; i++) {
    var p = getPluginInfo(tiddlers[i]);
    installedPlugins[i] = p;
    var n = p.Name;
    if (n)
      map[n] = p;
    n = p.Source;
    if (n)
      map[n] = p;
  }
  var visit = function(p) {
    if (!p || p.done)
      return;
    p.done = 1;
    var reqs = p.Requires;
    if (reqs) {
      reqs = reqs.readBracketedList();
      for (var i = 0; i < reqs.length; i++)
        visit(map[reqs[i]]);
    }
    toLoad.push(p);
  };
  for (i = 0; i < nPlugins; i++)
    visit(installedPlugins[i]);
  for (i = 0; i < toLoad.length; i++) {
    p = toLoad[i];
    pluginInfo = p;
    tiddler = p.tiddler;
    if (isPluginExecutable(p)) {
      if (isPluginEnabled(p)) {
        p.executed = true;
        var startTime = new Date();
        try {
          if (tiddler.text)
            window.eval(tiddler.text);
          nLoaded++;
        }
        catch(ex) {
          p.log.push(config.messages.pluginError.format([exceptionText(ex)]));
          p.error = true;
        }
        pluginInfo.startupTime = String((new Date()) - startTime) + "ms";
      }
      else {
        nPlugins--;
      }
    }
    else {
      p.warning = true;
    }
  }
  return nLoaded != nPlugins;
}

function getPluginInfo(tiddler)
{
  var p = store.getTiddlerSlices(tiddler.title, ["Name","Description","Version","Requires","CoreVersion","Date","Source","Author","License","Browsers"]);
  p.tiddler = tiddler;
  p.title = tiddler.title;
  p.log = [];
  return p;
}

// Check that a particular plugin is valid for execution
function isPluginExecutable(plugin)
{
  if (plugin.tiddler.isTagged("systemConfigForce")) {
    plugin.log.push(config.messages.pluginForced);
    return true;
  }
  if (plugin["CoreVersion"]) {
    var coreVersion = plugin["CoreVersion"].split(".");
    var w = parseInt(coreVersion[0], 10) - version.major;
    if (w == 0 && coreVersion[1])
      w = parseInt(coreVersion[1], 10) - version.minor;
    if (w == 0 && coreVersion[2])
      w = parseInt(coreVersion[2], 10) - version.revision;
    if (w > 0) {
      plugin.log.push(config.messages.pluginVersionError);
      return false;
    }
  }
  return true;
}

function isPluginEnabled(plugin)
{
  if (plugin.tiddler.isTagged("systemConfigDisable")) {
    plugin.log.push(config.messages.pluginDisabled);
    return false;
  }
  return true;
}

function invokeMacro(place, macro, params, wikifier, tiddler)
{
  try {
    var m = config.macros[macro];
    if (m && m.handler)
      m.handler(place, macro, params.readMacroParams(), wikifier, params, tiddler);
    else
      createTiddlyError(place, config.messages.macroError.format([macro]), config.messages.macroErrorDetails.format([macro,config.messages.missingMacro]));
  }
  catch(ex) {
    createTiddlyError(place, config.messages.macroError.format([macro]), config.messages.macroErrorDetails.format([macro,ex.toString()]));
  }
}

//--
//-- Paramifiers
//--

function getParameters()
{
  var p = null;
  if (window.location.hash) {
    p = decodeURIComponent(window.location.hash.substr(1));
    if (config.browser.firefoxDate != null && config.browser.firefoxDate[1] < "20051111")
      p = convertUTF8ToUnicode(p);
  }
  return p;
}

function invokeParamifier(params, handler)
{
  if (!params || params.length == undefined || params.length <= 1)
    return;
  for (var t = 1; t < params.length; t++) {
    var p = config.paramifiers[params[t].name];
    if (p && p[handler] instanceof Function)
      p[handler](params[t].value);
  }
}

config.paramifiers = {};

config.paramifiers.start = {
  oninit: function(v) {
    safeMode = v.toLowerCase() == "safe";
  }
};

config.paramifiers.open = {
  onstart: function(v) {
    if (!readOnly || store.tiddlerExists(v) || store.isShadowTiddler(v))
      story.displayTiddler("bottom", v, null, false, null);
  }
};

config.paramifiers.story = {
  onstart: function(v) {
    var list = store.getTiddlerText(v, "").parseParams("open", null, false);
    invokeParamifier(list, "onstart");
  }
};

config.paramifiers.search = {
  onstart: function(v) {
    story.search(v, false, false);
  }
};

config.paramifiers.searchRegExp = {
  onstart: function(v) {
    story.prototype.search(v, false, true);
  }
};

config.paramifiers.tag = {
  onstart: function(v) {
    story.displayTiddlers(null, store.filterTiddlers("[tag[" + v + "]]"), null, false, null);
  }
};

config.paramifiers.newTiddler = {
  onstart: function(v) {
    if (!readOnly) {
      story.displayTiddler(null, v, DEFAULT_EDIT_TEMPLATE);
      story.focusTiddler(v, "text");
    }
  }
};

config.paramifiers.newJournal = {
  onstart: function(v) {
    if (!readOnly) {
      var now = new Date();
      var title = now.formatString(v.trim());
      story.displayTiddler(null, title, DEFAULT_EDIT_TEMPLATE);
      story.focusTiddler(title, "text");
    }
  }
};

config.paramifiers.readOnly = {
  onconfig: function(v) {
    var p = v.toLowerCase();
    readOnly = p == "yes" ? true : (p == "no" ? false : readOnly);
  }
};

config.paramifiers.theme = {
  onconfig: function(v) {
    story.switchTheme(v);
  }
};

config.paramifiers.upgrade = {
  onstart: function(v) {
    upgradeFrom(v);
  }
};

config.paramifiers.recent = {
  onstart: function(v) {
    var titles = [];
    var tiddlers = store.getTiddlers("modified", "excludeLists").reverse();
    for (var i = 0; i < v && i < tiddlers.length; i++)
      titles.push(tiddlers[i].title);
    story.displayTiddlers(null, titles);
  }
};

config.paramifiers.filter = {
  onstart: function(v) {
    story.displayTiddlers(null, store.filterTiddlers(v), null, false);
  }
};

//--
//-- Formatter helpers
//--

function Formatter(formatters)
{
  this.formatters = [];
  var pattern = [];
  for (var n = 0; n < formatters.length; n++) {
    pattern.push("(" + formatters[n].match + ")");
    this.formatters.push(formatters[n]);
  }
  this.formatterRegExp = new RegExp(pattern.join("|"), "mg");
}

config.formatterHelpers = {

  createElementAndWikify: function(w)
  {
    w.subWikifyTerm(createTiddlyElement(w.output, this.element), this.termRegExp);
  },

  inlineCssHelper: function(w)
  {
    var styles = [];
    config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
    var lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
    while (lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
      var s,v;
      if (lookaheadMatch[1]) {
        s = lookaheadMatch[1].unDash();
        v = lookaheadMatch[2];
      }
      else {
        s = lookaheadMatch[3].unDash();
        v = lookaheadMatch[4];
      }
      if (s == "bgcolor")
        s = "backgroundColor";
      styles.push({style: s, value: v});
      w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
      config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
      lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
    }
    return styles;
  },

  applyCssHelper: function(e, styles)
  {
    for (var t = 0; t < styles.length; t++) {
      try {
        e.style[styles[t].style] = styles[t].value;
      }
      catch (ex) {
      }
    }
  },

  enclosedTextHelper: function(w)
  {
    this.lookaheadRegExp.lastIndex = w.matchStart;
    var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
    if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
      var text = lookaheadMatch[1];
      if (config.browser.isIE)
        text = text.replace(/\n/g, "\r");
      createTiddlyElement(w.output, this.element, null, null, text);
      w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
    }
  },

  isExternalLink: function(link)
  {
    if (store.tiddlerExists(link) || store.isShadowTiddler(link)) {
      return false;
    }
    var urlRegExp = new RegExp(config.textPrimitives.urlPattern, "mg");
    if (urlRegExp.exec(link)) {
      return true;
    }
    if (link.indexOf(".") != -1 || link.indexOf("\\") != -1 || link.indexOf("/") != -1 || link.indexOf("#") != -1) {
      return true;
    }
    return false;
  }

};

//--
//-- Standard formatters
//--

config.formatters = [
  {
    name: "table",
    match: "^\\|(?:[^\\n]*)\\|(?:[fhck]?)$",
    lookaheadRegExp: /^\|([^\n]*)\|([fhck]?)$/mg,
    rowTermRegExp: /(\|(?:[fhck]?)$\n?)/mg,
    cellRegExp: /(?:\|([^\n\|]*)\|)|(\|[fhck]?$\n?)/mg,
    cellTermRegExp: /((?:\x20*)\|)/mg,
    rowTypes: {"c":"caption", "h":"thead", "":"tbody", "f":"tfoot"},
    handler: function(w)
    {
      var table = createTiddlyElement(w.output, "table", null, "twtable");
      var prevColumns = [];
      var currRowType = null;
      var rowContainer;
      var rowCount = 0;
      w.nextMatch = w.matchStart;
      this.lookaheadRegExp.lastIndex = w.nextMatch;
      var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
      while (lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
        var nextRowType = lookaheadMatch[2];
        if (nextRowType == "k") {
          table.className = lookaheadMatch[1];
          w.nextMatch += lookaheadMatch[0].length + 1;
        }
        else {
          if (nextRowType != currRowType) {
            rowContainer = createTiddlyElement(table, this.rowTypes[nextRowType]);
            currRowType = nextRowType;
          }
          if (currRowType == "c") {
            // Caption
            w.nextMatch++;
            if (rowContainer != table.firstChild)
              table.insertBefore(rowContainer, table.firstChild);
            rowContainer.setAttribute("align", rowCount == 0 ? "top" : "bottom");
            w.subWikifyTerm(rowContainer, this.rowTermRegExp);
          }
          else {
            var theRow = createTiddlyElement(rowContainer, "tr", null, (rowCount & 1) ? "oddRow" : "evenRow");
            theRow.onmouseover = function() {addClass(this, "hoverRow");};
            theRow.onmouseout = function() {removeClass(this, "hoverRow");};
            this.rowHandler(w, theRow, prevColumns);
            rowCount++;
          }
        }
        this.lookaheadRegExp.lastIndex = w.nextMatch;
        lookaheadMatch = this.lookaheadRegExp.exec(w.source);
      }
    },
    rowHandler: function(w, e, prevColumns)
    {
      var col = 0;
      var colSpanCount = 1;
      var prevCell = null;
      this.cellRegExp.lastIndex = w.nextMatch;
      var cellMatch = this.cellRegExp.exec(w.source);
      while (cellMatch && cellMatch.index == w.nextMatch) {
        if (cellMatch[1] == "~") {
          // Rowspan
          var last = prevColumns[col];
          if (last) {
            last.rowSpanCount++;
            last.element.setAttribute("rowspan", last.rowSpanCount);
            last.element.setAttribute("rowSpan", last.rowSpanCount); // Needed for IE
            last.element.valign = "center";
          }
          w.nextMatch = this.cellRegExp.lastIndex - 1;
        }
        else if (cellMatch[1] == ">") {
          // Colspan
          colSpanCount++;
          w.nextMatch = this.cellRegExp.lastIndex - 1;
        }
        else if (cellMatch[2]) {
            // End of row
            if (prevCell && colSpanCount > 1) {
              prevCell.setAttribute("colspan", colSpanCount);
              prevCell.setAttribute("colSpan", colSpanCount); // Needed for IE
            }
            w.nextMatch = this.cellRegExp.lastIndex;
            break;
          }
          else {
            // Cell
            w.nextMatch++;
            var styles = config.formatterHelpers.inlineCssHelper(w);
            var spaceLeft = false;
            var chr = w.source.substr(w.nextMatch, 1);
            while (chr == " ") {
              spaceLeft = true;
              w.nextMatch++;
              chr = w.source.substr(w.nextMatch, 1);
            }
            var cell;
            if (chr == "!") {
              cell = createTiddlyElement(e, "th");
              w.nextMatch++;
            }
            else {
              cell = createTiddlyElement(e, "td");
            }
            prevCell = cell;
            prevColumns[col] = {rowSpanCount:1,element:cell};
            if (colSpanCount > 1) {
              cell.setAttribute("colspan", colSpanCount);
              cell.setAttribute("colSpan", colSpanCount); // Needed for IE
              colSpanCount = 1;
            }
            config.formatterHelpers.applyCssHelper(cell, styles);
            w.subWikifyTerm(cell, this.cellTermRegExp);
            if (w.matchText.substr(w.matchText.length - 2, 1) == " ") // spaceRight
              cell.align = spaceLeft ? "center" : "left";
            else if (spaceLeft)
              cell.align = "right";
            w.nextMatch--;
          }
        col++;
        this.cellRegExp.lastIndex = w.nextMatch;
        cellMatch = this.cellRegExp.exec(w.source);
      }
    }
  },

  {
    name: "heading",
    match: "^!{1,6}",
    termRegExp: /(\n)/mg,
    handler: function(w)
    {
      w.subWikifyTerm(createTiddlyElement(w.output, "h" + w.matchLength), this.termRegExp);
    }
  },

  {
    name: "list",
    match: "^(?:[\\*#;:]+)",
    lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg,
    termRegExp: /(\n)/mg,
    handler: function(w)
    {
      var stack = [w.output];
      var currLevel = 0, currType = null;
      var listLevel, listType, itemType, baseType;
      w.nextMatch = w.matchStart;
      this.lookaheadRegExp.lastIndex = w.nextMatch;
      var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
      while (lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
        if (lookaheadMatch[1]) {
          listType = "ul";
          itemType = "li";
        }
        else if (lookaheadMatch[2]) {
          listType = "ol";
          itemType = "li";
        }
        else if (lookaheadMatch[3]) {
            listType = "dl";
            itemType = "dt";
          }
          else if (lookaheadMatch[4]) {
              listType = "dl";
              itemType = "dd";
            }
        if (!baseType)
          baseType = listType;
        listLevel = lookaheadMatch[0].length;
        w.nextMatch += lookaheadMatch[0].length;
        var t;
        if (listLevel > currLevel) {
          for (t = currLevel; t < listLevel; t++) {
            var target = (currLevel == 0) ? stack[stack.length - 1] : stack[stack.length - 1].lastChild;
            stack.push(createTiddlyElement(target, listType));
          }
        }
        else if (listType != baseType && listLevel == 1) {
          w.nextMatch -= lookaheadMatch[0].length;
          return;
        }
        else if (listLevel < currLevel) {
            for (t = currLevel; t > listLevel; t--)
              stack.pop();
          }
          else if (listLevel == currLevel && listType != currType) {
              stack.pop();
              stack.push(createTiddlyElement(stack[stack.length - 1].lastChild, listType));
            }
        currLevel = listLevel;
        currType = listType;
        var e = createTiddlyElement(stack[stack.length - 1], itemType);
        w.subWikifyTerm(e, this.termRegExp);
        this.lookaheadRegExp.lastIndex = w.nextMatch;
        lookaheadMatch = this.lookaheadRegExp.exec(w.source);
      }
    }
  },

  {
    name: "quoteByBlock",
    match: "^<<<\\n",
    termRegExp: /(^<<<(\n|$))/mg,
    element: "blockquote",
    handler: config.formatterHelpers.createElementAndWikify
  },

  {
    name: "quoteByLine",
    match: "^>+",
    lookaheadRegExp: /^>+/mg,
    termRegExp: /(\n)/mg,
    element: "blockquote",
    handler: function(w)
    {
      var stack = [w.output];
      var currLevel = 0;
      var newLevel = w.matchLength;
      var t;
      do {
        if (newLevel > currLevel) {
          for (t = currLevel; t < newLevel; t++)
            stack.push(createTiddlyElement(stack[stack.length - 1], this.element));
        }
        else if (newLevel < currLevel) {
          for (t = currLevel; t > newLevel; t--)
            stack.pop();
        }
        currLevel = newLevel;
        w.subWikifyTerm(stack[stack.length - 1], this.termRegExp);
        createTiddlyElement(stack[stack.length - 1], "br");
        this.lookaheadRegExp.lastIndex = w.nextMatch;
        var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
        var matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
        if (matched) {
          newLevel = lookaheadMatch[0].length;
          w.nextMatch += lookaheadMatch[0].length;
        }
      }
      while (matched);
    }
  },

  {
    name: "rule",
    match: "^----+$\\n?",
    handler: function(w)
    {
      createTiddlyElement(w.output, "hr");
    }
  },

  {
    name: "monospacedByLine",
    match: "^(?:/\\*\\{\\{\\{\\*/|\\{\\{\\{|//\\{\\{\\{|<!--\\{\\{\\{-->)\\n",
    element: "pre",
    handler: function(w)
    {
      switch (w.matchText) {
        case "/*{{{*/\n": // CSS
          this.lookaheadRegExp = /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\/\*\}\}\}\*\/$\n?)/mg;
          break;
        case "{{{\n": // monospaced block
          this.lookaheadRegExp = /^\{\{\{\n((?:^[^\n]*\n)+?)(^\}\}\}$\n?)/mg;
          break;
        case "//{{{\n": // plugin
          this.lookaheadRegExp = /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\/\/\}\}\}$\n?)/mg;
          break;
        case "<!--{{{-->\n": //template
          this.lookaheadRegExp = /<!--\{\{\{-->\n*((?:^[^\n]*\n)+?)(\n*^<!--\}\}\}-->$\n?)/mg;
          break;
        default:
          break;
      }
      config.formatterHelpers.enclosedTextHelper.call(this, w);
    }
  },

  {
    name: "wikifyComment",
    match: "^(?:/\\*\\*\\*|<!---)\\n",
    handler: function(w)
    {
      var termRegExp = (w.matchText == "/***\n") ? (/(^\*\*\*\/\n)/mg) : (/(^--->\n)/mg);
      w.subWikifyTerm(w.output, termRegExp);
    }
  },

  {
    name: "macro",
    match: "<<",
    lookaheadRegExp: /<<([^>\s]+)(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg,
    handler: function(w)
    {
      this.lookaheadRegExp.lastIndex = w.matchStart;
      var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
      if (lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) {
        w.nextMatch = this.lookaheadRegExp.lastIndex;
        invokeMacro(w.output, lookaheadMatch[1], lookaheadMatch[2], w, w.tiddler);
      }
    }
  },

  {
    name: "prettyLink",
    match: "\\[\\[",
    lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg,
    handler: function(w)
    {
      this.lookaheadRegExp.lastIndex = w.matchStart;
      var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
      if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
        var e;
        var text = lookaheadMatch[1];
        if (lookaheadMatch[3]) {
          // Pretty bracketted link
          var link = lookaheadMatch[3];
          e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link)) ?
              createExternalLink(w.output, link) : createTiddlyLink(w.output, decodeURIComponent(link), false, null, w.isStatic, w.tiddler);
        }
        else {
          // Simple bracketted link
          e = createTiddlyLink(w.output, decodeURIComponent(text), false, null, w.isStatic, w.tiddler);
        }
        createTiddlyText(e, text);
        w.nextMatch = this.lookaheadRegExp.lastIndex;
      }
    }
  },

  {
    name: "wikiLink",
    match: config.textPrimitives.unWikiLink + "?" + config.textPrimitives.wikiLink,
    handler: function(w)
    {
      if (w.matchText.substr(0, 1) == config.textPrimitives.unWikiLink) {
        w.outputText(w.output, w.matchStart + 1, w.nextMatch);
        return;
      }
      if (w.matchStart > 0) {
        var preRegExp = new RegExp(config.textPrimitives.anyLetterStrict, "mg");
        preRegExp.lastIndex = w.matchStart - 1;
        var preMatch = preRegExp.exec(w.source);
        if (preMatch.index == w.matchStart - 1) {
          w.outputText(w.output, w.matchStart, w.nextMatch);
          return;
        }
      }
      if (w.autoLinkWikiWords || store.isShadowTiddler(w.matchText)) {
        var link = createTiddlyLink(w.output, w.matchText, false, null, w.isStatic, w.tiddler);
        w.outputText(link, w.matchStart, w.nextMatch);
      }
      else {
        w.outputText(w.output, w.matchStart, w.nextMatch);
      }
    }
  },

  {
    name: "urlLink",
    match: config.textPrimitives.urlPattern,
    handler: function(w)
    {
      w.outputText(createExternalLink(w.output, w.matchText), w.matchStart, w.nextMatch);
    }
  },

  {
    name: "image",
    match: "\\[[<>]?[Ii][Mm][Gg]\\[",
    lookaheadRegExp: /\[([<]?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg,
    handler: function(w)
    {
      this.lookaheadRegExp.lastIndex = w.matchStart;
      var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
      if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
        var e = w.output;
        if (lookaheadMatch[5]) {
          var link = lookaheadMatch[5];
          e = config.formatterHelpers.isExternalLink(link) ? createExternalLink(w.output, link) : createTiddlyLink(w.output, link, false, null, w.isStatic, w.tiddler);
          addClass(e, "imageLink");
        }
        var img = createTiddlyElement(e, "img");
        if (lookaheadMatch[1])
          img.align = "left";
        else if (lookaheadMatch[2])
          img.align = "right";
        if (lookaheadMatch[3]) {
          img.title = lookaheadMatch[3];
          img.setAttribute("alt", lookaheadMatch[3]);
        }
        img.src = lookaheadMatch[4];
        w.nextMatch = this.lookaheadRegExp.lastIndex;
      }
    }
  },

  {
    name: "html",
    match: "<[Hh][Tt][Mm][Ll]>",
    lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg,
    handler: function(w)
    {
      this.lookaheadRegExp.lastIndex = w.matchStart;
      var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
      if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
        createTiddlyElement(w.output, "span").innerHTML = lookaheadMatch[1];
        w.nextMatch = this.lookaheadRegExp.lastIndex;
      }
    }
  },

  {
    name: "commentByBlock",
    match: "/%",
    lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg,
    handler: function(w)
    {
      this.lookaheadRegExp.lastIndex = w.matchStart;
      var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
      if (lookaheadMatch && lookaheadMatch.index == w.matchStart)
        w.nextMatch = this.lookaheadRegExp.lastIndex;
    }
  },

  {
    name: "characterFormat",
    match: "''|//|__|\\^\\^|~~|--(?!\\s|$)|\\{\\{\\{",
    handler: function(w)
    {
      switch (w.matchText) {
        case "''":
          w.subWikifyTerm(w.output.appendChild(document.createElement("strong")), /('')/mg);
          break;
        case "//":
          w.subWikifyTerm(createTiddlyElement(w.output, "em"), /(\/\/)/mg);
          break;
        case "__":
          w.subWikifyTerm(createTiddlyElement(w.output, "u"), /(__)/mg);
          break;
        case "^^":
          w.subWikifyTerm(createTiddlyElement(w.output, "sup"), /(\^\^)/mg);
          break;
        case "~~":
          w.subWikifyTerm(createTiddlyElement(w.output, "sub"), /(~~)/mg);
          break;
        case "--":
          w.subWikifyTerm(createTiddlyElement(w.output, "strike"), /(--)/mg);
          break;
        case "{{{":
          var lookaheadRegExp = /\{\{\{((?:.|\n)*?)\}\}\}/mg;
          lookaheadRegExp.lastIndex = w.matchStart;
          var lookaheadMatch = lookaheadRegExp.exec(w.source);
          if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
            createTiddlyElement(w.output, "code", null, null, lookaheadMatch[1]);
            w.nextMatch = lookaheadRegExp.lastIndex;
          }
          break;
      }
    }
  },

  {
    name: "customFormat",
    match: "@@|\\{\\{",
    handler: function(w)
    {
      switch (w.matchText) {
        case "@@":
          var e = createTiddlyElement(w.output, "span");
          var styles = config.formatterHelpers.inlineCssHelper(w);
          if (styles.length == 0)
            e.className = "marked";
          else
            config.formatterHelpers.applyCssHelper(e, styles);
          w.subWikifyTerm(e, /(@@)/mg);
          break;
        case "{{":
          var lookaheadRegExp = /\{\{[\s]*([\w]+[\s\w]*)[\s]*\{(\n?)/mg;
          lookaheadRegExp.lastIndex = w.matchStart;
          var lookaheadMatch = lookaheadRegExp.exec(w.source);
          if (lookaheadMatch) {
            w.nextMatch = lookaheadRegExp.lastIndex;
            e = createTiddlyElement(w.output, lookaheadMatch[2] == "\n" ? "div" : "span", null, lookaheadMatch[1]);
            w.subWikifyTerm(e, /(\}\}\})/mg);
          }
          break;
      }
    }
  },

  {
    name: "mdash",
    match: "--",
    handler: function(w)
    {
      createTiddlyElement(w.output, "span").innerHTML = "&mdash;";
    }
  },

  {
    name: "lineBreak",
    match: "\\n|<br ?/?>",
    handler: function(w)
    {
      createTiddlyElement(w.output, "br");
    }
  },

  {
    name: "rawText",
    match: "\\\"{3}|<nowiki>",
    lookaheadRegExp: /(?:\"{3}|<nowiki>)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg,
    handler: function(w)
    {
      this.lookaheadRegExp.lastIndex = w.matchStart;
      var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
      if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
        createTiddlyElement(w.output, "span", null, null, lookaheadMatch[1]);
        w.nextMatch = this.lookaheadRegExp.lastIndex;
      }
    }
  },

  {
    name: "htmlEntitiesEncoding",
    match: "(?:(?:&#?[a-zA-Z0-9]{2,8};|.)(?:&#?(?:x0*(?:3[0-6][0-9a-fA-F]|1D[c-fC-F][0-9a-fA-F]|20[d-fD-F][0-9a-fA-F]|FE2[0-9a-fA-F])|0*(?:76[89]|7[7-9][0-9]|8[0-7][0-9]|761[6-9]|76[2-7][0-9]|84[0-3][0-9]|844[0-7]|6505[6-9]|6506[0-9]|6507[0-1]));)+|&#?[a-zA-Z0-9]{2,8};)",
    handler: function(w)
    {
      createTiddlyElement(w.output, "span").innerHTML = w.matchText;
    }
  }

];

//--
//-- Wikifier
//--

function getParser(tiddler, format)
{
  if (tiddler) {
    if (!format)
      format = tiddler.fields["wikiformat"];
    var i;
    if (format) {
      for (i in config.parsers) {
        if (format == config.parsers[i].format)
          return config.parsers[i];
      }
    }
    else {
      for (i in config.parsers) {
        if (tiddler.isTagged(config.parsers[i].formatTag))
          return config.parsers[i];
      }
    }
  }
  return formatter;
}

function wikify(source, output, highlightRegExp, tiddler)
{
  if (source) {
    var wikifier = new Wikifier(source, getParser(tiddler), highlightRegExp, tiddler);
    var t0 = new Date();
    wikifier.subWikify(output);
    if (tiddler && config.options.chkDisplayInstrumentation)
      displayMessage("wikify:" + tiddler.title + " in " + (new Date() - t0) + " ms");
  }
}

function wikifyStatic(source, highlightRegExp, tiddler, format)
{
  var e = createTiddlyElement(document.body, "pre");
  e.style.display = "none";
  var html = "";
  if (source && source != "") {
    if (!tiddler)
      tiddler = new Tiddler("temp");
    var wikifier = new Wikifier(source, getParser(tiddler, format), highlightRegExp, tiddler);
    wikifier.isStatic = true;
    wikifier.subWikify(e);
    html = e.innerHTML;
    removeNode(e);
  }
  return html;
}

function wikifyPlain(title, theStore, limit)
{
  if (!theStore)
    theStore = store;
  if (theStore.tiddlerExists(title) || theStore.isShadowTiddler(title)) {
    return wikifyPlainText(theStore.getTiddlerText(title), limit, tiddler);
  }
  else {
    return "";
  }
}

function wikifyPlainText(text, limit, tiddler)
{
  if (limit > 0)
    text = text.substr(0, limit);
  var wikifier = new Wikifier(text, formatter, null, tiddler);
  return wikifier.wikifyPlain();
}

function highlightify(source, output, highlightRegExp, tiddler)
{
  if (source) {
    var wikifier = new Wikifier(source, formatter, highlightRegExp, tiddler);
    wikifier.outputText(output, 0, source.length);
  }
}

function Wikifier(source, formatter, highlightRegExp, tiddler)
{
  this.source = source;
  this.output = null;
  this.formatter = formatter;
  this.nextMatch = 0;
  this.autoLinkWikiWords = tiddler && tiddler.autoLinkWikiWords() == false ? false : true;
  this.highlightRegExp = highlightRegExp;
  this.highlightMatch = null;
  this.isStatic = false;
  if (highlightRegExp) {
    highlightRegExp.lastIndex = 0;
    this.highlightMatch = highlightRegExp.exec(source);
  }
  this.tiddler = tiddler;
}

Wikifier.prototype.wikifyPlain = function()
{
  var e = createTiddlyElement(document.body, "div");
  e.style.display = "none";
  this.subWikify(e);
  var text = getPlainText(e);
  removeNode(e);
  return text;
};

Wikifier.prototype.subWikify = function(output, terminator)
{
  try {
    if (terminator)
      this.subWikifyTerm(output, new RegExp("(" + terminator + ")", "mg"));
    else
      this.subWikifyUnterm(output);
  }
  catch(ex) {
    showException(ex);
  }
};

Wikifier.prototype.subWikifyUnterm = function(output)
{
  var oldOutput = this.output;
  this.output = output;
  this.formatter.formatterRegExp.lastIndex = this.nextMatch;
  var formatterMatch = this.formatter.formatterRegExp.exec(this.source);
  while (formatterMatch) {
    // Output any text before the match
    if (formatterMatch.index > this.nextMatch)
      this.outputText(this.output, this.nextMatch, formatterMatch.index);
    // Set the match parameters for the handler
    this.matchStart = formatterMatch.index;
    this.matchLength = formatterMatch[0].length;
    this.matchText = formatterMatch[0];
    this.nextMatch = this.formatter.formatterRegExp.lastIndex;
    for (var t = 1; t < formatterMatch.length; t++) {
      if (formatterMatch[t]) {
        this.formatter.formatters[t - 1].handler(this);
        this.formatter.formatterRegExp.lastIndex = this.nextMatch;
        break;
      }
    }
    formatterMatch = this.formatter.formatterRegExp.exec(this.source);
  }
  if (this.nextMatch < this.source.length) {
    this.outputText(this.output, this.nextMatch, this.source.length);
    this.nextMatch = this.source.length;
  }
  this.output = oldOutput;
};

Wikifier.prototype.subWikifyTerm = function(output, terminatorRegExp)
{
  var oldOutput = this.output;
  this.output = output;
  terminatorRegExp.lastIndex = this.nextMatch;
  var terminatorMatch = terminatorRegExp.exec(this.source);
  this.formatter.formatterRegExp.lastIndex = this.nextMatch;
  var formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0, terminatorMatch.index) : this.source);
  while (terminatorMatch || formatterMatch) {
    if (terminatorMatch && (!formatterMatch || terminatorMatch.index <= formatterMatch.index)) {
      if (terminatorMatch.index > this.nextMatch)
        this.outputText(this.output, this.nextMatch, terminatorMatch.index);
      this.matchText = terminatorMatch[1];
      this.matchLength = terminatorMatch[1].length;
      this.matchStart = terminatorMatch.index;
      this.nextMatch = this.matchStart + this.matchLength;
      this.output = oldOutput;
      return;
    }
    if (formatterMatch.index > this.nextMatch)
      this.outputText(this.output, this.nextMatch, formatterMatch.index);
    this.matchStart = formatterMatch.index;
    this.matchLength = formatterMatch[0].length;
    this.matchText = formatterMatch[0];
    this.nextMatch = this.formatter.formatterRegExp.lastIndex;
    for (var t = 1; t < formatterMatch.length; t++) {
      if (formatterMatch[t]) {
        this.formatter.formatters[t - 1].handler(this);
        this.formatter.formatterRegExp.lastIndex = this.nextMatch;
        break;
      }
    }
    terminatorRegExp.lastIndex = this.nextMatch;
    terminatorMatch = terminatorRegExp.exec(this.source);
    formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0, terminatorMatch.index) : this.source);
  }
  if (this.nextMatch < this.source.length) {
    this.outputText(this.output, this.nextMatch, this.source.length);
    this.nextMatch = this.source.length;
  }
  this.output = oldOutput;
};

Wikifier.prototype.outputText = function(place, startPos, endPos)
{
  while (this.highlightMatch && (this.highlightRegExp.lastIndex > startPos) && (this.highlightMatch.index < endPos) && (startPos < endPos)) {
    if (this.highlightMatch.index > startPos) {
      createTiddlyText(place, this.source.substring(startPos, this.highlightMatch.index));
      startPos = this.highlightMatch.index;
    }
    var highlightEnd = Math.min(this.highlightRegExp.lastIndex, endPos);
    var theHighlight = createTiddlyElement(place, "span", null, "highlight", this.source.substring(startPos, highlightEnd));
    startPos = highlightEnd;
    if (startPos >= this.highlightRegExp.lastIndex)
      this.highlightMatch = this.highlightRegExp.exec(this.source);
  }
  if (startPos < endPos) {
    createTiddlyText(place, this.source.substring(startPos, endPos));
  }
};

//--
//-- Macro definitions
//--

config.macros.today.handler = function(place, macroName, params)
{
  var now = new Date();
  var text = params[0] ? now.formatString(params[0].trim()) : now.toLocaleString();
  createTiddlyElement(place, "span", null, null, text);
};

config.macros.version.handler = function(place)
{
  createTiddlyElement(place, "span", null, null, formatVersion());
};

config.macros.list.handler = function(place, macroName, params)
{
  var type = params[0] || "all";
  var list = document.createElement("ul");
  place.appendChild(list);
  if (this[type].prompt)
    createTiddlyElement(list, "li", null, "listTitle", this[type].prompt);
  var results;
  if (this[type].handler)
    results = this[type].handler(params);
  for (var t = 0; t < results.length; t++) {
    var li = document.createElement("li");
    list.appendChild(li);
    createTiddlyLink(li, typeof results[t] == "string" ? results[t] : results[t].title, true);
  }
};

config.macros.list.all.handler = function(params)
{
  return store.reverseLookup("tags", "excludeLists", false, "title");
};

config.macros.list.missing.handler = function(params)
{
  return store.getMissingLinks();
};

config.macros.list.orphans.handler = function(params)
{
  return store.getOrphans();
};

config.macros.list.shadowed.handler = function(params)
{
  return store.getShadowed();
};

config.macros.list.touched.handler = function(params)
{
  return store.getTouched();
};

config.macros.list.filter.handler = function(params)
{
  var filter = params[1];
  var results = [];
  if (filter) {
    var tiddlers = store.filterTiddlers(filter);
    for (var t = 0; t < tiddlers.length; t++)
      results.push(tiddlers[t].title);
  }
  return results;
};

config.macros.allTags.handler = function(place, macroName, params)
{
  var tags = store.getTags(params[0]);
  var ul = createTiddlyElement(place, "ul");
  if (tags.length == 0)
    createTiddlyElement(ul, "li", null, "listTitle", this.noTags);
  for (var t = 0; t < tags.length; t++) {
    var title = tags[t][0];
    var info = getTiddlyLinkInfo(title);
    var li = createTiddlyElement(ul, "li");
    var btn = createTiddlyButton(li, title + " (" + tags[t][1] + ")", this.tooltip.format([title]), onClickTag, info.classes);
    btn.setAttribute("tag", title);
    btn.setAttribute("refresh", "link");
    btn.setAttribute("tiddlyLink", title);
  }
};

config.macros.timeline.handler = function(place, macroName, params)
{
  var field = params[0] || "modified";
  var tiddlers = store.reverseLookup("tags", "excludeLists", false, field);
  var lastDay = "";
  var last = params[1] ? tiddlers.length - Math.min(tiddlers.length, parseInt(params[1])) : 0;
  var dateFormat = params[2] || this.dateFormat;
  for (var t = tiddlers.length - 1; t >= last; t--) {
    var tiddler = tiddlers[t];
    var theDay = tiddler[field].convertToLocalYYYYMMDDHHMM().substr(0, 8);
    if (theDay != lastDay) {
      var ul = document.createElement("ul");
      place.appendChild(ul);
      createTiddlyElement(ul, "li", null, "listTitle", tiddler[field].formatString(dateFormat));
      lastDay = theDay;
    }
    createTiddlyElement(ul, "li", null, "listLink").appendChild(createTiddlyLink(place, tiddler.title, true));
  }
};

config.macros.tiddler.handler = function(place, macroName, params, wikifier, paramString, tiddler)
{
  params = paramString.parseParams("name", null, true, false, true);
  var names = params[0]["name"];
  var tiddlerName = names[0];
  var className = names[1] || null;
  var args = params[0]["with"];
  var wrapper = createTiddlyElement(place, "span", null, className);
  if (!args) {
    wrapper.setAttribute("refresh", "content");
    wrapper.setAttribute("tiddler", tiddlerName);
  }
  var text = store.getTiddlerText(tiddlerName);
  if (text) {
    var stack = config.macros.tiddler.tiddlerStack;
    if (stack.indexOf(tiddlerName) !== -1)
      return;
    stack.push(tiddlerName);
    try {
      var n = args ? Math.min(args.length, 9) : 0;
      for (var i = 0; i < n; i++) {
        var placeholderRE = new RegExp("\\$" + (i + 1), "mg");
        text = text.replace(placeholderRE, args[i]);
      }
      config.macros.tiddler.renderText(wrapper, text, tiddlerName, params);
    }
    finally {
      stack.pop();
    }
  }
};

config.macros.tiddler.renderText = function(place, text, tiddlerName, params)
{
  wikify(text, place, null, store.getTiddler(tiddlerName));
};

config.macros.tiddler.tiddlerStack = [];

config.macros.tag.handler = function(place, macroName, params)
{
  createTagButton(place, params[0], null, params[1], params[2]);
};

config.macros.tags.handler = function(place, macroName, params, wikifier, paramString, tiddler)
{
  params = paramString.parseParams("anon", null, true, false, false);
  var ul = createTiddlyElement(place, "ul");
  var title = getParam(params, "anon", "");
  if (title && store.tiddlerExists(title))
    tiddler = store.getTiddler(title);
  var sep = getParam(params, "sep", " ");
  var lingo = config.views.wikified.tag;
  var prompt = tiddler.tags.length == 0 ? lingo.labelNoTags : lingo.labelTags;
  createTiddlyElement(ul, "li", null, "listTitle", prompt.format([tiddler.title]));
  for (var t = 0; t < tiddler.tags.length; t++) {
    createTagButton(createTiddlyElement(ul, "li"), tiddler.tags[t], tiddler.title);
    if (t < tiddler.tags.length - 1)
      createTiddlyText(ul, sep);
  }
};

config.macros.tagging.handler = function(place, macroName, params, wikifier, paramString, tiddler)
{
  params = paramString.parseParams("anon", null, true, false, false);
  var ul = createTiddlyElement(place, "ul");
  var title = getParam(params, "anon", "");
  if (title == "" && tiddler instanceof Tiddler)
    title = tiddler.title;
  var sep = getParam(params, "sep", " ");
  ul.setAttribute("title", this.tooltip.format([title]));
  var tagged = store.getTaggedTiddlers(title);
  var prompt = tagged.length == 0 ? this.labelNotTag : this.label;
  createTiddlyElement(ul, "li", null, "listTitle", prompt.format([title,tagged.length]));
  for (var t = 0; t < tagged.length; t++) {
    createTiddlyLink(createTiddlyElement(ul, "li"), tagged[t].title, true);
    if (t < tagged.length - 1)
      createTiddlyText(ul, sep);
  }
};

config.macros.closeAll.handler = function(place)
{
  createTiddlyButton(place, this.label, this.prompt, this.onClick);
};

config.macros.closeAll.onClick = function(e)
{
  story.closeAllTiddlers();
  return false;
};

config.macros.permaview.handler = function(place)
{
  createTiddlyButton(place, this.label, this.prompt, this.onClick);
};

config.macros.permaview.onClick = function(e)
{
  story.permaView();
  return false;
};

config.macros.saveChanges.handler = function(place, macroName, params)
{
  if (!readOnly)
    createTiddlyButton(place, params[0] || this.label, params[1] || this.prompt, this.onClick, null, null, this.accessKey);
};

config.macros.saveChanges.onClick = function(e)
{
  saveChanges();
  return false;
};

config.macros.slider.onClickSlider = function(ev)
{
  var e = ev || window.event;
  var n = this.nextSibling;
  var cookie = n.getAttribute("cookie");
  var isOpen = n.style.display != "none";
  if (config.options.chkAnimate && anim && typeof Slider == "function")
    anim.startAnimating(new Slider(n, !isOpen, null, "none"));
  else
    n.style.display = isOpen ? "none" : "block";
  config.options[cookie] = !isOpen;
  saveOptionCookie(cookie);
  return false;
};

config.macros.slider.createSlider = function(place, cookie, title, tooltip)
{
  var c = cookie || "";
  var btn = createTiddlyButton(place, title, tooltip, this.onClickSlider);
  var panel = createTiddlyElement(null, "div", null, "sliderPanel");
  panel.setAttribute("cookie", c);
  panel.style.display = config.options[c] ? "block" : "none";
  place.appendChild(panel);
  return panel;
};

config.macros.slider.handler = function(place, macroName, params)
{
  var panel = this.createSlider(place, params[0], params[2], params[3]);
  var text = store.getTiddlerText(params[1]);
  panel.setAttribute("refresh", "content");
  panel.setAttribute("tiddler", params[1]);
  if (text)
    wikify(text, panel, null, store.getTiddler(params[1]));
};

// <<gradient [[tiddler name]] vert|horiz rgb rgb rgb rgb... >>
config.macros.gradient.handler = function(place, macroName, params, wikifier, paramString, tiddler)
{
  var panel = wikifier ? createTiddlyElement(place, "div", null, "gradient") : place;
  panel.style.position = "relative";
  panel.style.overflow = "hidden";
  panel.style.zIndex = "0";
  if (wikifier) {
    var styles = config.formatterHelpers.inlineCssHelper(wikifier);
    config.formatterHelpers.applyCssHelper(panel, styles);
  }
  params = paramString.parseParams("color");
  var locolors = [], hicolors = [];
  for (var t = 2; t < params.length; t++) {
    var c = new RGB(params[t].value);
    if (params[t].name == "snap") {
      hicolors[hicolors.length - 1] = c;
    }
    else {
      locolors.push(c);
      hicolors.push(c);
    }
  }
  drawGradient(panel, params[1].value != "vert", locolors, hicolors);
  if (wikifier)
    wikifier.subWikify(panel, ">>");
  if (document.all) {
    panel.style.height = "100%";
    panel.style.width = "100%";
  }
};

config.macros.message.handler = function(place, macroName, params)
{
  if (params[0]) {
    var names = params[0].split(".");
    var lookupMessage = function(root, nameIndex) {
      if (names[nameIndex] in root) {
        if (nameIndex < names.length - 1)
          return (lookupMessage(root[names[nameIndex]], nameIndex + 1));
        else
          return root[names[nameIndex]];
      }
      else
        return null;
    };
    var m = lookupMessage(config, 0);
    if (m == null)
      m = lookupMessage(window, 0);
    createTiddlyText(place, m.toString().format(params.splice(1)));
  }
};


config.macros.view.views = {
  text: function(value, place, params, wikifier, paramString, tiddler) {
    highlightify(value, place, highlightHack, tiddler);
  },
  link: function(value, place, params, wikifier, paramString, tiddler) {
    createTiddlyLink(place, value, true);
  },
  wikified: function(value, place, params, wikifier, paramString, tiddler) {
    if (params[2])
      value = params[2].unescapeLineBreaks().format([value]);
    wikify(value, place, highlightHack, tiddler);
  },
  date: function(value, place, params, wikifier, paramString, tiddler) {
    value = Date.convertFromYYYYMMDDHHMM(value);
    createTiddlyText(place, value.formatString(params[2] ? params[2] : config.views.wikified.dateFormat));
  }
};

config.macros.view.handler = function(place, macroName, params, wikifier, paramString, tiddler)
{
  if ((tiddler instanceof Tiddler) && params[0]) {
    var value = store.getValue(tiddler, params[0]);
    if (value) {
      var type = params[1] || config.macros.view.defaultView;
      var handler = config.macros.view.views[type];
      if (handler)
        handler(value, place, params, wikifier, paramString, tiddler);
    }
  }
};

config.macros.edit.handler = function(place, macroName, params, wikifier, paramString, tiddler)
{
  var field = params[0];
  var rows = params[1] || 0;
  var defVal = params[2] || '';
  if ((tiddler instanceof Tiddler) && field) {
    story.setDirty(tiddler.title, true);
    var e,v;
    if (field != "text" && !rows) {
      e = createTiddlyElement(null, "input");
      if (tiddler.isReadOnly())
        e.setAttribute("readOnly", "readOnly");
      e.setAttribute("edit", field);
      e.setAttribute("type", "text");
      e.value = store.getValue(tiddler, field) || defVal;
      e.setAttribute("size", "40");
      e.setAttribute("autocomplete", "off");
      place.appendChild(e);
    }
    else {
      var wrapper1 = createTiddlyElement(null, "fieldset", null, "fieldsetFix");
      var wrapper2 = createTiddlyElement(wrapper1, "div");
      e = createTiddlyElement(wrapper2, "textarea");
      if (tiddler.isReadOnly())
        e.setAttribute("readOnly", "readOnly");
      e.value = v = store.getValue(tiddler, field) || defVal;
      rows = rows || 10;
      var lines = v.match(/\n/mg);
      var maxLines = Math.max(parseInt(config.options.txtMaxEditRows), 5);
      if (lines != null && lines.length > rows)
        rows = lines.length + 5;
      rows = Math.min(rows, maxLines);
      e.setAttribute("rows", rows);
      e.setAttribute("edit", field);
      place.appendChild(wrapper1);
    }
    return e;
  }
};

config.macros.tagChooser.onClick = function(ev)
{
  var e = ev || window.event;
  var lingo = config.views.editor.tagChooser;
  var popup = Popup.create(this);
  var tags = store.getTags("excludeLists");
  if (tags.length == 0)
    createTiddlyText(createTiddlyElement(popup, "li"), lingo.popupNone);
  for (var t = 0; t < tags.length; t++) {
    var tag = createTiddlyButton(createTiddlyElement(popup, "li"), tags[t][0], lingo.tagTooltip.format([tags[t][0]]), config.macros.tagChooser.onTagClick);
    tag.setAttribute("tag", tags[t][0]);
    tag.setAttribute("tiddler", this.getAttribute("tiddler"));
  }
  Popup.show();
  e.cancelBubble = true;
  if (e.stopPropagation) e.stopPropagation();
  return false;
};

config.macros.tagChooser.onTagClick = function(ev)
{
  var e = ev || window.event;
  if (e.metaKey || e.ctrlKey) stopEvent(e); //# keep popup open on CTRL-click
  var tag = this.getAttribute("tag");
  var title = this.getAttribute("tiddler");
  if (!readOnly)
    story.setTiddlerTag(title, tag, 0);
  return false;
};

config.macros.tagChooser.handler = function(place, macroName, params, wikifier, paramString, tiddler)
{
  if (tiddler instanceof Tiddler) {
    var lingo = config.views.editor.tagChooser;
    var btn = createTiddlyButton(place, lingo.text, lingo.tooltip, this.onClick);
    btn.setAttribute("tiddler", tiddler.title);
  }
};

config.macros.refreshDisplay.handler = function(place)
{
  createTiddlyButton(place, this.label, this.prompt, this.onClick);
};

config.macros.refreshDisplay.onClick = function(e)
{
  refreshAll();
  return false;
};

config.macros.annotations.handler = function(place, macroName, params, wikifier, paramString, tiddler)
{
  var title = tiddler ? tiddler.title : null;
  var a = title ? config.annotations[title] : null;
  if (!tiddler || !title || !a)
    return;
  var text = a.format([title]);
  wikify(text, createTiddlyElement(place, "div", null, "annotation"), null, tiddler);
};

//--
//-- NewTiddler and NewJournal macros
//--

config.macros.newTiddler.createNewTiddlerButton = function(place, title, params, label, prompt, accessKey, newFocus, isJournal)
{
  var tags = [];
  for (var t = 1; t < params.length; t++) {
    if ((params[t].name == "anon" && t != 1) || (params[t].name == "tag"))
      tags.push(params[t].value);
  }
  label = getParam(params, "label", label);
  prompt = getParam(params, "prompt", prompt);
  accessKey = getParam(params, "accessKey", accessKey);
  newFocus = getParam(params, "focus", newFocus);
  var customFields = getParam(params, "fields", "");
  if (!customFields && !store.isShadowTiddler(title))
    customFields = String.encodeHashMap(config.defaultCustomFields);
  var btn = createTiddlyButton(place, label, prompt, this.onClickNewTiddler, null, null, accessKey);
  btn.setAttribute("newTitle", title);
  btn.setAttribute("isJournal", isJournal ? "true" : "false");
  if (tags.length > 0)
    btn.setAttribute("params", tags.join("|"));
  btn.setAttribute("newFocus", newFocus);
  btn.setAttribute("newTemplate", getParam(params, "template", DEFAULT_EDIT_TEMPLATE));
  if (customFields !== "")
    btn.setAttribute("customFields", customFields);
  var text = getParam(params, "text");
  if (text !== undefined)
    btn.setAttribute("newText", text);
  return btn;
};

config.macros.newTiddler.onClickNewTiddler = function()
{
  var title = this.getAttribute("newTitle");
  if (this.getAttribute("isJournal") == "true") {
    title = new Date().formatString(title.trim());
  }
  var params = this.getAttribute("params");
  var tags = params ? params.split("|") : [];
  var focus = this.getAttribute("newFocus");
  var template = this.getAttribute("newTemplate");
  var customFields = this.getAttribute("customFields");
  if (!customFields && !store.isShadowTiddler(title))
    customFields = String.encodeHashMap(config.defaultCustomFields);
  story.displayTiddler(null, title, template, false, null, null);
  var tiddlerElem = story.getTiddler(title);
  if (customFields)
    story.addCustomFields(tiddlerElem, customFields);
  var text = this.getAttribute("newText");
  if (typeof text == "string")
    story.getTiddlerField(title, "text").value = text.format([title]);
  for (var t = 0; t < tags.length; t++)
    story.setTiddlerTag(title, tags[t], +1);
  story.focusTiddler(title, focus);
  return false;
};

config.macros.newTiddler.handler = function(place, macroName, params, wikifier, paramString)
{
  if (!readOnly) {
    params = paramString.parseParams("anon", null, true, false, false);
    var title = params[1] && params[1].name == "anon" ? params[1].value : this.title;
    title = getParam(params, "title", title);
    this.createNewTiddlerButton(place, title, params, this.label, this.prompt, this.accessKey, "title", false);
  }
};

config.macros.newJournal.handler = function(place, macroName, params, wikifier, paramString)
{
  if (!readOnly) {
    params = paramString.parseParams("anon", null, true, false, false);
    var title = params[1] && params[1].name == "anon" ? params[1].value : config.macros.timeline.dateFormat;
    title = getParam(params, "title", title);
    config.macros.newTiddler.createNewTiddlerButton(place, title, params, this.label, this.prompt, this.accessKey, "text", true);
  }
};

//--
//-- Search macro
//--

config.macros.search.handler = function(place, macroName, params)
{
  var searchTimeout = null;
  var btn = createTiddlyButton(place, this.label, this.prompt, this.onClick, "searchButton");
  var txt = createTiddlyElement(place, "input", null, "txtOptionInput searchField");
  if (params[0])
    txt.value = params[0];
  txt.onkeyup = this.onKeyPress;
  txt.onfocus = this.onFocus;
  txt.setAttribute("size", this.sizeTextbox);
  txt.setAttribute("accessKey", this.accessKey);
  txt.setAttribute("autocomplete", "off");
  txt.setAttribute("lastSearchText", "");
  if (config.browser.isSafari) {
    txt.setAttribute("type", "search");
    txt.setAttribute("results", "5");
  }
  else {
    txt.setAttribute("type", "text");
  }
};

// Global because there's only ever one outstanding incremental search timer
config.macros.search.timeout = null;

config.macros.search.doSearch = function(txt)
{
  if (txt.value.length > 0) {
    story.search(txt.value, config.options.chkCaseSensitiveSearch, config.options.chkRegExpSearch);
    txt.setAttribute("lastSearchText", txt.value);
  }
};

config.macros.search.onClick = function(e)
{
  config.macros.search.doSearch(this.nextSibling);
  return false;
};

config.macros.search.onKeyPress = function(ev)
{
  var e = ev || window.event;
  switch (e.keyCode) {
    case 13: // Ctrl-Enter
    case 10: // Ctrl-Enter on IE PC
      config.macros.search.doSearch(this);
      break;
    case 27: // Escape
      this.value = "";
      clearMessage();
      break;
  }
  if (config.options.chkIncrementalSearch) {
    if (this.value.length > 2) {
      if (this.value != this.getAttribute("lastSearchText")) {
        if (config.macros.search.timeout)
          clearTimeout(config.macros.search.timeout);
        var txt = this;
        config.macros.search.timeout = setTimeout(function() {config.macros.search.doSearch(txt);}, 500);
      }
    }
    else {
      if (config.macros.search.timeout)
        clearTimeout(config.macros.search.timeout);
    }
  }
};

config.macros.search.onFocus = function(e)
{
  this.select();
};

//--
//-- Tabs macro
//--

config.macros.tabs.handler = function(place, macroName, params)
{
  var cookie = params[0];
  var numTabs = (params.length - 1) / 3;
  var wrapper = createTiddlyElement(null, "div", null, "tabsetWrapper " + cookie);
  var tabset = createTiddlyElement(wrapper, "div", null, "tabset");
  tabset.setAttribute("cookie", cookie);
  var validTab = false;
  for (var t = 0; t < numTabs; t++) {
    var label = params[t * 3 + 1];
    var prompt = params[t * 3 + 2];
    var content = params[t * 3 + 3];
    var tab = createTiddlyButton(tabset, label, prompt, this.onClickTab, "tab tabUnselected");
    tab.setAttribute("tab", label);
    tab.setAttribute("content", content);
    tab.title = prompt;
    if (config.options[cookie] == label)
      validTab = true;
  }
  if (!validTab)
    config.options[cookie] = params[1];
  place.appendChild(wrapper);
  this.switchTab(tabset, config.options[cookie]);
};

config.macros.tabs.onClickTab = function(e)
{
  config.macros.tabs.switchTab(this.parentNode, this.getAttribute("tab"));
  return false;
};

config.macros.tabs.switchTab = function(tabset, tab)
{
  var cookie = tabset.getAttribute("cookie");
  var theTab = null;
  var nodes = tabset.childNodes;
  for (var t = 0; t < nodes.length; t++) {
    if (nodes[t].getAttribute && nodes[t].getAttribute("tab") == tab) {
      theTab = nodes[t];
      theTab.className = "tab tabSelected";
    }
    else {
      nodes[t].className = "tab tabUnselected";
    }
  }
  if (theTab) {
    if (tabset.nextSibling && tabset.nextSibling.className == "tabContents")
      removeNode(tabset.nextSibling);
    var tabContent = createTiddlyElement(null, "div", null, "tabContents");
    tabset.parentNode.insertBefore(tabContent, tabset.nextSibling);
    var contentTitle = theTab.getAttribute("content");
    wikify(store.getTiddlerText(contentTitle), tabContent, null, store.getTiddler(contentTitle));
    if (cookie) {
      config.options[cookie] = tab;
      saveOptionCookie(cookie);
    }
  }
};

//--
//-- Tiddler toolbar
//--

// Create a toolbar command button
config.macros.toolbar.createCommand = function(place, commandName, tiddler, className)
{
  if (typeof commandName != "string") {
    var c = null;
    for (var t in config.commands) {
      if (config.commands[t] == commandName)
        c = t;
    }
    commandName = c;
  }
  if ((tiddler instanceof Tiddler) && (typeof commandName == "string")) {
    var command = config.commands[commandName];
    if (command.isEnabled ? command.isEnabled(tiddler) : this.isCommandEnabled(command, tiddler)) {
      var text = command.getText ? command.getText(tiddler) : this.getCommandText(command, tiddler);
      var tooltip = command.getTooltip ? command.getTooltip(tiddler) : this.getCommandTooltip(command, tiddler);
      var cmd;
      switch (command.type) {
        case "popup":
          cmd = this.onClickPopup;
          break;
        case "command":
        default:
          cmd = this.onClickCommand;
          break;
      }
      var btn = createTiddlyButton(null, text, tooltip, cmd);
      btn.setAttribute("commandName", commandName);
      btn.setAttribute("tiddler", tiddler.title);
      if (className)
        addClass(btn, className);
      place.appendChild(btn);
    }
  }
};

config.macros.toolbar.isCommandEnabled = function(command, tiddler)
{
  var title = tiddler.title;
  var ro = tiddler.isReadOnly();
  var shadow = store.isShadowTiddler(title) && !store.tiddlerExists(title);
  return (!ro || (ro && !command.hideReadOnly)) && !(shadow && command.hideShadow);
};

config.macros.toolbar.getCommandText = function(command, tiddler)
{
  return tiddler.isReadOnly() && command.readOnlyText || command.text;
};

config.macros.toolbar.getCommandTooltip = function(command, tiddler)
{
  return tiddler.isReadOnly() && command.readOnlyTooltip || command.tooltip;
};

config.macros.toolbar.onClickCommand = function(ev)
{
  var e = ev || window.event;
  e.cancelBubble = true;
  if (e.stopPropagation) e.stopPropagation();
  var command = config.commands[this.getAttribute("commandName")];
  return command.handler(e, this, this.getAttribute("tiddler"));
};

config.macros.toolbar.onClickPopup = function(ev)
{
  var e = ev || window.event;
  e.cancelBubble = true;
  if (e.stopPropagation) e.stopPropagation();
  var popup = Popup.create(this);
  var command = config.commands[this.getAttribute("commandName")];
  var title = this.getAttribute("tiddler");
  var tiddler = store.fetchTiddler(title);
  popup.setAttribute("tiddler", title);
  command.handlePopup(popup, title);
  Popup.show();
  return false;
};

// Invoke the first command encountered from a given place that is tagged with a specified class
config.macros.toolbar.invokeCommand = function(place, className, event)
{
  var children = place.getElementsByTagName("a");
  for (var t = 0; t < children.length; t++) {
    var c = children[t];
    if (hasClass(c, className) && c.getAttribute && c.getAttribute("commandName")) {
      if (c.onclick instanceof Function)
        c.onclick.call(c, event);
      break;
    }
  }
};

config.macros.toolbar.onClickMore = function(ev)
{
  var e = this.nextSibling;
  e.style.display = "inline";
  removeNode(this);
  return false;
};

config.macros.toolbar.handler = function(place, macroName, params, wikifier, paramString, tiddler)
{
  for (var t = 0; t < params.length; t++) {
    var c = params[t];
    switch (c) {
      case '>':
        var btn = createTiddlyButton(place, this.moreLabel, this.morePrompt, config.macros.toolbar.onClickMore);
        addClass(btn, "moreCommand");
        var e = createTiddlyElement(place, "span", null, "moreCommand");
        e.style.display = "none";
        place = e;
        break;
      default:
        var className = "";
        switch (c.substr(0, 1)) {
          case "+":
            className = "defaultCommand";
            c = c.substr(1);
            break;
          case "-":
            className = "cancelCommand";
            c = c.substr(1);
            break;
        }
        if (c in config.commands)
          this.createCommand(place, c, tiddler, className);
        break;
    }
  }
};

//--
//-- Menu and toolbar commands
//--

config.commands.closeTiddler.handler = function(event, src, title)
{
  if (story.isDirty(title) && !readOnly) {
    if (!confirm(config.commands.cancelTiddler.warning.format([title])))
      return false;
  }
  story.setDirty(title, false);
  story.closeTiddler(title, true);
  return false;
};

config.commands.closeOthers.handler = function(event, src, title)
{
  story.closeAllTiddlers(title);
  return false;
};

config.commands.editTiddler.handler = function(event, src, title)
{
  clearMessage();
  var tiddlerElem = story.getTiddler(title);
  var fields = tiddlerElem.getAttribute("tiddlyFields");
  story.displayTiddler(null, title, DEFAULT_EDIT_TEMPLATE, false, null, fields);
  story.focusTiddler(title, config.options.txtEditorFocus || "text");
  return false;
};

config.commands.saveTiddler.handler = function(event, src, title)
{
  var newTitle = story.saveTiddler(title, event.shiftKey);
  if (newTitle)
    story.displayTiddler(null, newTitle);
  return false;
};

config.commands.cancelTiddler.handler = function(event, src, title)
{
  if (story.hasChanges(title) && !readOnly) {
    if (!confirm(this.warning.format([title])))
      return false;
  }
  story.setDirty(title, false);
  story.displayTiddler(null, title);
  return false;
};

config.commands.deleteTiddler.handler = function(event, src, title)
{
  var deleteIt = true;
  if (config.options.chkConfirmDelete)
    deleteIt = confirm(this.warning.format([title]));
  if (deleteIt) {
    store.removeTiddler(title);
    story.closeTiddler(title, true);
    autoSaveChanges();
  }
  return false;
};

config.commands.permalink.handler = function(event, src, title)
{
  var t = encodeURIComponent(String.encodeTiddlyLink(title));
  if (window.location.hash != t)
    window.location.hash = t;
  return false;
};

config.commands.references.handlePopup = function(popup, title)
{
  var references = store.getReferringTiddlers(title);
  var c = false;
  for (var r = 0; r < references.length; r++) {
    if (references[r].title != title && !references[r].isTagged("excludeLists")) {
      createTiddlyLink(createTiddlyElement(popup, "li"), references[r].title, true);
      c = true;
    }
  }
  if (!c)
    createTiddlyText(createTiddlyElement(popup, "li", null, "disabled"), this.popupNone);
};

config.commands.jump.handlePopup = function(popup, title)
{
  story.forEachTiddler(function(title, element) {
    createTiddlyLink(createTiddlyElement(popup, "li"), title, true, null, false, null, true);
  });
};

config.commands.syncing.handlePopup = function(popup, title)
{
  var tiddler = store.fetchTiddler(title);
  if (!tiddler)
    return;
  var serverType = tiddler.getServerType();
  var serverHost = tiddler.fields['server.host'];
  var serverWorkspace = tiddler.fields['server.workspace'];
  if (!serverWorkspace)
    serverWorkspace = "";
  if (serverType) {
    var e = createTiddlyElement(popup, "li", null, "popupMessage");
    e.innerHTML = config.commands.syncing.currentlySyncing.format([serverType,serverHost,serverWorkspace]);
  }
  else {
    createTiddlyElement(popup, "li", null, "popupMessage", config.commands.syncing.notCurrentlySyncing);
  }
  if (serverType) {
    createTiddlyElement(createTiddlyElement(popup, "li", null, "listBreak"), "div");
    var btn = createTiddlyButton(createTiddlyElement(popup, "li"), this.captionUnSync, null, config.commands.syncing.onChooseServer);
    btn.setAttribute("tiddler", title);
    btn.setAttribute("server.type", "");
  }
  createTiddlyElement(createTiddlyElement(popup, "li", null, "listBreak"), "div");
  createTiddlyElement(popup, "li", null, "popupMessage", config.commands.syncing.chooseServer);
  var feeds = store.getTaggedTiddlers("systemServer", "title");
  for (var t = 0; t < feeds.length; t++) {
    var f = feeds[t];
    var feedServerType = store.getTiddlerSlice(f.title, "Type");
    if (!feedServerType)
      feedServerType = "file";
    var feedServerHost = store.getTiddlerSlice(f.title, "URL");
    if (!feedServerHost)
      feedServerHost = "";
    var feedServerWorkspace = store.getTiddlerSlice(f.title, "Workspace");
    if (!feedServerWorkspace)
      feedServerWorkspace = "";
    var caption = f.title;
    if (serverType == feedServerType && serverHost == feedServerHost && serverWorkspace == feedServerWorkspace) {
      caption = config.commands.syncing.currServerMarker + caption;
    }
    else {
      caption = config.commands.syncing.notCurrServerMarker + caption;
    }
    btn = createTiddlyButton(createTiddlyElement(popup, "li"), caption, null, config.commands.syncing.onChooseServer);
    btn.setAttribute("tiddler", title);
    btn.setAttribute("server.type", feedServerType);
    btn.setAttribute("server.host", feedServerHost);
    btn.setAttribute("server.workspace", feedServerWorkspace);
  }
};

config.commands.syncing.onChooseServer = function(e)
{
  var tiddler = this.getAttribute("tiddler");
  var serverType = this.getAttribute("server.type");
  if (serverType) {
    store.addTiddlerFields(tiddler, {
      "server.type": serverType,
      "server.host": this.getAttribute("server.host"),
      "server.workspace": this.getAttribute("server.workspace")
    });
  }
  else {
    store.setValue(tiddler, "server", null);
  }
  return false;
};

config.commands.fields.handlePopup = function(popup, title)
{
  var tiddler = store.fetchTiddler(title);
  if (!tiddler)
    return;
  var fields = {};
  store.forEachField(tiddler, function(tiddler, fieldName, value) {fields[fieldName] = value;}, true);
  var items = [];
  for (var t in fields) {
    items.push({field: t,value: fields[t]});
  }
  items.sort(function(a, b) {return a.field < b.field ? -1 : (a.field == b.field ? 0 : +1);});
  if (items.length > 0)
    ListView.create(popup, items, this.listViewTemplate);
  else
    createTiddlyElement(popup, "div", null, null, this.emptyText);
};

//--
//-- Tiddler() object
//--

function Tiddler(title)
{
  this.title = title;
  this.text = "";
  this.modifier = null;
  this.created = new Date();
  this.modified = this.created;
  this.links = [];
  this.linksUpdated = false;
  this.tags = [];
  this.fields = {};
  return this;
}

Tiddler.prototype.getLinks = function()
{
  if (this.linksUpdated == false)
    this.changed();
  return this.links;
};

// Returns the fields that are inherited in string field:"value" field2:"value2" format
Tiddler.prototype.getInheritedFields = function()
{
  var f = {};
  for (var i in this.fields) {
    if (i == "server.host" || i == "server.workspace" || i == "wikiformat" || i == "server.type") {
      f[i] = this.fields[i];
    }
  }
  return String.encodeHashMap(f);
};

// Increment the changeCount of a tiddler
Tiddler.prototype.incChangeCount = function()
{
  var c = this.fields['changecount'];
  c = c ? parseInt(c, 10) : 0;
  this.fields['changecount'] = String(c + 1);
};

// Clear the changeCount of a tiddler
Tiddler.prototype.clearChangeCount = function()
{
  if (this.fields['changecount']) {
    delete this.fields['changecount'];
  }
};

Tiddler.prototype.doNotSave = function()
{
  return this.fields['doNotSave'];
};

// Returns true if the tiddler has been updated since the tiddler was created or downloaded
Tiddler.prototype.isTouched = function()
{
  var changeCount = this.fields['changecount'];
  if (changeCount === undefined)
    changeCount = 0;
  return changeCount > 0;
};

// Return the tiddler as an RSS item
Tiddler.prototype.toRssItem = function(uri)
{
  var s = [];
  s.push("<title" + ">" + this.title.htmlEncode() + "</title" + ">");
  s.push("<description>" + wikifyStatic(this.text, null, this).htmlEncode() + "</description>");
  for (var t = 0; t < this.tags.length; t++)
    s.push("<category>" + this.tags[t] + "</category>");
  s.push("<link>" + uri + "#" + encodeURIComponent(String.encodeTiddlyLink(this.title)) + "</link>");
  s.push("<pubDate>" + this.modified.toGMTString() + "</pubDate>");
  return s.join("\n");
};

// Format the text for storage in an RSS item
Tiddler.prototype.saveToRss = function(uri)
{
  return "<item>\n" + this.toRssItem(uri) + "\n</item>";
};

// Change the text and other attributes of a tiddler
Tiddler.prototype.set = function(title, text, modifier, modified, tags, created, fields)
{
  this.assign(title, text, modifier, modified, tags, created, fields);
  this.changed();
  return this;
};

// Change the text and other attributes of a tiddler without triggered a tiddler.changed() call
Tiddler.prototype.assign = function(title, text, modifier, modified, tags, created, fields)
{
  if (title != undefined)
    this.title = title;
  if (text != undefined)
    this.text = text;
  if (modifier != undefined)
    this.modifier = modifier;
  if (modified != undefined)
    this.modified = modified;
  if (created != undefined)
    this.created = created;
  if (fields != undefined)
    this.fields = fields;
  if (tags != undefined)
    this.tags = (typeof tags == "string") ? tags.readBracketedList() : tags;
  else if (this.tags == undefined)
    this.tags = [];
  return this;
};

// Get the tags for a tiddler as a string (space delimited, using [[brackets]] for tags containing spaces)
Tiddler.prototype.getTags = function()
{
  return String.encodeTiddlyLinkList(this.tags);
};

// Test if a tiddler carries a tag
Tiddler.prototype.isTagged = function(tag)
{
  return this.tags.indexOf(tag) != -1;
};

// Static method to convert "\n" to newlines, "\s" to "\"
Tiddler.unescapeLineBreaks = function(text)
{
  return text ? text.unescapeLineBreaks() : "";
};

// Convert newlines to "\n", "\" to "\s"
Tiddler.prototype.escapeLineBreaks = function()
{
  return this.text.escapeLineBreaks();
};

// Updates the secondary information (like links[] array) after a change to a tiddler
Tiddler.prototype.changed = function()
{
  this.links = [];
  var t = this.autoLinkWikiWords() ? 0 : 1;
  var tiddlerLinkRegExp = t == 0 ? config.textPrimitives.tiddlerAnyLinkRegExp : config.textPrimitives.tiddlerForcedLinkRegExp;
  tiddlerLinkRegExp.lastIndex = 0;
  var formatMatch = tiddlerLinkRegExp.exec(this.text);
  while (formatMatch) {
    var lastIndex = tiddlerLinkRegExp.lastIndex;
    if (t == 0 && formatMatch[1] && formatMatch[1] != this.title) {
      // wikiWordLink
      if (formatMatch.index > 0) {
        var preRegExp = new RegExp(config.textPrimitives.unWikiLink + "|" + config.textPrimitives.anyLetter, "mg");
        preRegExp.lastIndex = formatMatch.index - 1;
        var preMatch = preRegExp.exec(this.text);
        if (preMatch.index != formatMatch.index - 1)
          this.links.pushUnique(formatMatch[1]);
      }
      else {
        this.links.pushUnique(formatMatch[1]);
      }
    }
    else if (formatMatch[2 - t] && !config.formatterHelpers.isExternalLink(formatMatch[3 - t])) // titledBrackettedLink
      this.links.pushUnique(formatMatch[3 - t]);
    else if (formatMatch[4 - t] && formatMatch[4 - t] != this.title) // brackettedLink
        this.links.pushUnique(formatMatch[4 - t]);
    tiddlerLinkRegExp.lastIndex = lastIndex;
    formatMatch = tiddlerLinkRegExp.exec(this.text);
  }
  this.linksUpdated = true;
};

Tiddler.prototype.getSubtitle = function()
{
  var modifier = this.modifier;
  if (!modifier)
    modifier = config.messages.subtitleUnknown;
  var modified = this.modified;
  if (modified)
    modified = modified.toLocaleString();
  else
    modified = config.messages.subtitleUnknown;
  return config.messages.tiddlerLinkTooltip.format([this.title,modifier,modified]);
};

Tiddler.prototype.isReadOnly = function()
{
  return readOnly;
};

Tiddler.prototype.autoLinkWikiWords = function()
{
  return !(this.isTagged("systemConfig") || this.isTagged("excludeMissing"));
};

Tiddler.prototype.generateFingerprint = function()
{
  return "0x" + Crypto.hexSha1Str(this.text);
};

Tiddler.prototype.getServerType = function()
{
  var serverType = null;
  if (this.fields['server.type'])
    serverType = this.fields['server.type'];
  if (!serverType)
    serverType = this.fields['wikiformat'];
  if (serverType && !config.adaptors[serverType])
    serverType = null;
  return serverType;
};

Tiddler.prototype.getAdaptor = function()
{
  var serverType = this.getServerType();
  return serverType ? new config.adaptors[serverType]() : null;
};

//--
//-- TiddlyWiki() object contains Tiddler()s
//--

function TiddlyWiki()
{
  var tiddlers = {}; // Hashmap by name of tiddlers
  this.tiddlersUpdated = false;
  this.namedNotifications = []; // Array of {name:,notify:} of notification functions
  this.notificationLevel = 0;
  this.slices = {}; // map tiddlerName->(map sliceName->sliceValue). Lazy.
  this.clear = function() {
    tiddlers = {};
    this.setDirty(false);
  };
  this.fetchTiddler = function(title) {
    var t = tiddlers[title];
    return t instanceof Tiddler ? t : null;
  };
  this.deleteTiddler = function(title) {
    delete this.slices[title];
    delete tiddlers[title];
  };
  this.addTiddler = function(tiddler) {
    delete this.slices[tiddler.title];
    tiddlers[tiddler.title] = tiddler;
  };
  this.forEachTiddler = function(callback) {
    for (var t in tiddlers) {
      var tiddler = tiddlers[t];
      if (tiddler instanceof Tiddler)
        callback.call(this, t, tiddler);
    }
  };
}

TiddlyWiki.prototype.setDirty = function(dirty)
{
  this.dirty = dirty;
};

TiddlyWiki.prototype.isDirty = function()
{
  return this.dirty;
};

TiddlyWiki.prototype.tiddlerExists = function(title)
{
  var t = this.fetchTiddler(title);
  return t != undefined;
};

TiddlyWiki.prototype.isShadowTiddler = function(title)
{
  return typeof config.shadowTiddlers[title] == "string";
};

TiddlyWiki.prototype.createTiddler = function(title)
{
  var tiddler = this.fetchTiddler(title);
  if (!tiddler) {
    tiddler = new Tiddler(title);
    this.addTiddler(tiddler);
    this.setDirty(true);
  }
  return tiddler;
};

TiddlyWiki.prototype.getTiddler = function(title)
{
  var t = this.fetchTiddler(title);
  if (t != undefined)
    return t;
  else
    return null;
};

TiddlyWiki.prototype.getTiddlerText = function(title, defaultText)
{
  if (!title)
    return defaultText;
  var pos = title.indexOf(config.textPrimitives.sectionSeparator);
  var section = null;
  if (pos != -1) {
    section = title.substr(pos + config.textPrimitives.sectionSeparator.length);
    title = title.substr(0, pos);
  }
  pos = title.indexOf(config.textPrimitives.sliceSeparator);
  if (pos != -1) {
    var slice = this.getTiddlerSlice(title.substr(0, pos), title.substr(pos + config.textPrimitives.sliceSeparator.length));
    if (slice)
      return slice;
  }
  var tiddler = this.fetchTiddler(title);
  if (tiddler) {
    if (!section)
      return tiddler.text;
    var re = new RegExp("(^!{1,6}" + section.escapeRegExp() + "[ \t]*\n)", "mg");
    re.lastIndex = 0;
    var match = re.exec(tiddler.text);
    if (match) {
      var t = tiddler.text.substr(match.index + match[1].length);
      var re2 = /^!/mg;
      re2.lastIndex = 0;
      match = re2.exec(t); //# search for the next heading
      if (match)
        t = t.substr(0, match.index - 1);//# don't include final \n
      return t;
    }
    return defaultText;
  }
  if (this.isShadowTiddler(title))
    return config.shadowTiddlers[title];
  if (defaultText != undefined)
    return defaultText;
  return null;
};

TiddlyWiki.prototype.getRecursiveTiddlerText = function(title, defaultText, depth)
{
  var bracketRegExp = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])", "mg");
  var text = this.getTiddlerText(title, null);
  if (text == null)
    return defaultText;
  var textOut = [];
  var lastPos = 0;
  do {
    var match = bracketRegExp.exec(text);
    if (match) {
      textOut.push(text.substr(lastPos, match.index - lastPos));
      if (match[1]) {
        if (depth <= 0)
          textOut.push(match[1]);
        else
          textOut.push(this.getRecursiveTiddlerText(match[1], "[[" + match[1] + "]]", depth - 1));
      }
      lastPos = match.index + match[0].length;
    }
    else {
      textOut.push(text.substr(lastPos));
    }
  }
  while (match);
  return textOut.join("");
};

TiddlyWiki.prototype.slicesRE = /(?:^([\'\/]{0,2})~?([\.\w]+)\:\1\s*([^\n]+)\s*$)|(?:^\|([\'\/]{0,2})~?([\.\w]+)\:?\4\|\s*([^\|\n]+)\s*\|$)/gm;

// @internal
TiddlyWiki.prototype.calcAllSlices = function(title)
{
  var slices = {};
  var text = this.getTiddlerText(title, "");
  this.slicesRE.lastIndex = 0;
  var m = this.slicesRE.exec(text);
  while (m) {
    if (m[2])
      slices[m[2]] = m[3];
    else
      slices[m[5]] = m[6];
    m = this.slicesRE.exec(text);
  }
  return slices;
};

// Returns the slice of text of the given name
TiddlyWiki.prototype.getTiddlerSlice = function(title, sliceName)
{
  var slices = this.slices[title];
  if (!slices) {
    slices = this.calcAllSlices(title);
    this.slices[title] = slices;
  }
  return slices[sliceName];
};

// Build an hashmap of the specified named slices of a tiddler
TiddlyWiki.prototype.getTiddlerSlices = function(title, sliceNames)
{
  var r = {};
  for (var t = 0; t < sliceNames.length; t++) {
    var slice = this.getTiddlerSlice(title, sliceNames[t]);
    if (slice)
      r[sliceNames[t]] = slice;
  }
  return r;
};

TiddlyWiki.prototype.suspendNotifications = function()
{
  this.notificationLevel--;
};

TiddlyWiki.prototype.resumeNotifications = function()
{
  this.notificationLevel++;
};

// Invoke the notification handlers for a particular tiddler
TiddlyWiki.prototype.notify = function(title, doBlanket)
{
  if (!this.notificationLevel) {
    for (var t = 0; t < this.namedNotifications.length; t++) {
      var n = this.namedNotifications[t];
      if ((n.name == null && doBlanket) || (n.name == title))
        n.notify(title);
    }
  }
};

// Invoke the notification handlers for all tiddlers
TiddlyWiki.prototype.notifyAll = function()
{
  if (!this.notificationLevel) {
    for (var t = 0; t < this.namedNotifications.length; t++) {
      var n = this.namedNotifications[t];
      if (n.name)
        n.notify(n.name);
    }
  }
};

// Add a notification handler to a tiddler
TiddlyWiki.prototype.addNotification = function(title, fn)
{
  for (var i = 0; i < this.namedNotifications.length; i++) {
    if ((this.namedNotifications[i].name == title) && (this.namedNotifications[i].notify == fn))
      return this;
  }
  this.namedNotifications.push({name: title, notify: fn});
  return this;
};

TiddlyWiki.prototype.removeTiddler = function(title)
{
  var tiddler = this.fetchTiddler(title);
  if (tiddler) {
    this.deleteTiddler(title);
    this.notify(title, true);
    this.setDirty(true);
  }
};

// Reset the sync status of a freshly synced tiddler
TiddlyWiki.prototype.resetTiddler = function(title)
{
  var tiddler = this.fetchTiddler(title);
  if (tiddler) {
    tiddler.clearChangeCount();
    this.notify(title, true);
    this.setDirty(true);
  }
};

TiddlyWiki.prototype.setTiddlerTag = function(title, status, tag)
{
  var tiddler = this.fetchTiddler(title);
  if (tiddler) {
    var t = tiddler.tags.indexOf(tag);
    if (t != -1)
      tiddler.tags.splice(t, 1);
    if (status)
      tiddler.tags.push(tag);
    tiddler.changed();
    tiddler.incChangeCount(title);
    this.notify(title, true);
    this.setDirty(true);
  }
};

TiddlyWiki.prototype.addTiddlerFields = function(title, fields)
{
  var tiddler = this.fetchTiddler(title);
  if (!tiddler)
    return;
  merge(tiddler.fields, fields);
  tiddler.changed();
  tiddler.incChangeCount(title);
  this.notify(title, true);
  this.setDirty(true);
};

TiddlyWiki.prototype.saveTiddler = function(title, newTitle, newBody, modifier, modified, tags, fields, clearChangeCount, created)
{
  var tiddler = this.fetchTiddler(title);
  if (tiddler) {
    created = created || tiddler.created; // Preserve created date
    this.deleteTiddler(title);
  }
  else {
    created = created || modified;
    tiddler = new Tiddler();
  }
  tiddler.set(newTitle, newBody, modifier, modified, tags, created, fields);
  this.addTiddler(tiddler);
  if (clearChangeCount)
    tiddler.clearChangeCount();
  else
    tiddler.incChangeCount();
  if (title != newTitle)
    this.notify(title, true);
  this.notify(newTitle, true);
  this.setDirty(true);
  return tiddler;
};

TiddlyWiki.prototype.incChangeCount = function(title)
{
  var tiddler = this.fetchTiddler(title);
  if (tiddler)
    tiddler.incChangeCount();
};

TiddlyWiki.prototype.getLoader = function()
{
  if (!this.loader)
    this.loader = new TW21Loader();
  return this.loader;
};

TiddlyWiki.prototype.getSaver = function()
{
  if (!this.saver)
    this.saver = new TW21Saver();
  return this.saver;
};

// Return all tiddlers formatted as an HTML string
TiddlyWiki.prototype.allTiddlersAsHtml = function()
{
  return this.getSaver().externalize(store);
};

// Load contents of a TiddlyWiki from an HTML DIV
TiddlyWiki.prototype.loadFromDiv = function(src, idPrefix, noUpdate)
{
  this.idPrefix = idPrefix;
  var storeElem = (typeof src == "string") ? document.getElementById(src) : src;
  if (!storeElem)
    return;
  var tiddlers = this.getLoader().loadTiddlers(this, storeElem.childNodes);
  this.setDirty(false);
  if (!noUpdate) {
    for (var i = 0; i < tiddlers.length; i++)
      tiddlers[i].changed();
  }
};

// Load contents of a TiddlyWiki from a string
// Returns null if there's an error
TiddlyWiki.prototype.importTiddlyWiki = function(text)
{
  var posDiv = locateStoreArea(text);
  if (!posDiv)
    return null;
  var content = "<" + "html><" + "body>" + text.substring(posDiv[0], posDiv[1] + endSaveArea.length) + "<" + "/body><" + "/html>";
  // Create the iframe
  var iframe = document.createElement("iframe");
  iframe.style.display = "none";
  document.body.appendChild(iframe);
  var doc = iframe.document;
  if (iframe.contentDocument)
    doc = iframe.contentDocument; // For NS6
  else if (iframe.contentWindow)
    doc = iframe.contentWindow.document; // For IE5.5 and IE6
  // Put the content in the iframe
  doc.open();
  doc.writeln(content);
  doc.close();
  // Load the content into a TiddlyWiki() object
  var storeArea = doc.getElementById("storeArea");
  this.loadFromDiv(storeArea, "store");
  // Get rid of the iframe
  iframe.parentNode.removeChild(iframe);
  return this;
};

TiddlyWiki.prototype.updateTiddlers = function()
{
  this.tiddlersUpdated = true;
  this.forEachTiddler(function(title, tiddler) {
    tiddler.changed();
  });
};

// Return an array of tiddlers matching a search regular expression
TiddlyWiki.prototype.search = function(searchRegExp, sortField, excludeTag, match)
{
  var candidates = this.reverseLookup("tags", excludeTag, !!match);
  var results = [];
  for (var t = 0; t < candidates.length; t++) {
    if ((candidates[t].title.search(searchRegExp) != -1) || (candidates[t].text.search(searchRegExp) != -1))
      results.push(candidates[t]);
  }
  if (!sortField)
    sortField = "title";
  results.sort(function(a, b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
  return results;
};

// Returns a list of all tags in use
//   excludeTag - if present, excludes tags that are themselves tagged with excludeTag
// Returns an array of arrays where [tag][0] is the name of the tag and [tag][1] is the number of occurances
TiddlyWiki.prototype.getTags = function(excludeTag)
{
  var results = [];
  this.forEachTiddler(function(title, tiddler) {
    for (var g = 0; g < tiddler.tags.length; g++) {
      var tag = tiddler.tags[g];
      var n = true;
      for (var c = 0; c < results.length; c++) {
        if (results[c][0] == tag) {
          n = false;
          results[c][1]++;
        }
      }
      if (n && excludeTag) {
        var t = this.fetchTiddler(tag);
        if (t && t.isTagged(excludeTag))
          n = false;
      }
      if (n)
        results.push([tag,1]);
    }
  });
  results.sort(function(a, b) {return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : (a[0].toLowerCase() == b[0].toLowerCase() ? 0 : +1);});
  return results;
};

// Return an array of the tiddlers that are tagged with a given tag
TiddlyWiki.prototype.getTaggedTiddlers = function(tag, sortField)
{
  return this.reverseLookup("tags", tag, true, sortField);
};

// Return an array of the tiddlers that link to a given tiddler
TiddlyWiki.prototype.getReferringTiddlers = function(title, unusedParameter, sortField)
{
  if (!this.tiddlersUpdated)
    this.updateTiddlers();
  return this.reverseLookup("links", title, true, sortField);
};

// Return an array of the tiddlers that do or do not have a specified entry in the specified storage array (ie, "links" or "tags")
// lookupMatch == true to match tiddlers, false to exclude tiddlers
TiddlyWiki.prototype.reverseLookup = function(lookupField, lookupValue, lookupMatch, sortField)
{
  var results = [];
  this.forEachTiddler(function(title, tiddler) {
    var f = !lookupMatch;
    for (var lookup = 0; lookup < tiddler[lookupField].length; lookup++) {
      if (tiddler[lookupField][lookup] == lookupValue)
        f = lookupMatch;
    }
    if (f)
      results.push(tiddler);
  });
  if (!sortField)
    sortField = "title";
  results.sort(function(a, b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
  return results;
};

// Return the tiddlers as a sorted array
TiddlyWiki.prototype.getTiddlers = function(field, excludeTag)
{
  var results = [];
  this.forEachTiddler(function(title, tiddler) {
    if (excludeTag == undefined || !tiddler.isTagged(excludeTag))
      results.push(tiddler);
  });
  if (field)
    results.sort(function(a, b) {return a[field] < b[field] ? -1 : (a[field] == b[field] ? 0 : +1);});
  return results;
};

// Return array of names of tiddlers that are referred to but not defined
TiddlyWiki.prototype.getMissingLinks = function(sortField)
{
  if (!this.tiddlersUpdated)
    this.updateTiddlers();
  var results = [];
  this.forEachTiddler(function (title, tiddler) {
    if (tiddler.isTagged("excludeMissing") || tiddler.isTagged("systemConfig"))
      return;
    for (var n = 0; n < tiddler.links.length; n++) {
      var link = tiddler.links[n];
      if (this.fetchTiddler(link) == null && !this.isShadowTiddler(link))
        results.pushUnique(link);
    }
  });
  results.sort();
  return results;
};

// Return an array of names of tiddlers that are defined but not referred to
TiddlyWiki.prototype.getOrphans = function()
{
  var results = [];
  this.forEachTiddler(function (title, tiddler) {
    if (this.getReferringTiddlers(title).length == 0 && !tiddler.isTagged("excludeLists"))
      results.push(title);
  });
  results.sort();
  return results;
};

// Return an array of names of all the shadow tiddlers
TiddlyWiki.prototype.getShadowed = function()
{
  var results = [];
  for (var t in config.shadowTiddlers) {
    if (typeof config.shadowTiddlers[t] == "string")
      results.push(t);
  }
  results.sort();
  return results;
};

// Return an array of tiddlers that have been touched since they were downloaded or created
TiddlyWiki.prototype.getTouched = function()
{
  var results = [];
  this.forEachTiddler(function(title, tiddler) {
    if (tiddler.isTouched())
      results.push(tiddler);
  });
  results.sort();
  return results;
};

// Resolves a Tiddler reference or tiddler title into a Tiddler object, or null if it doesn't exist
TiddlyWiki.prototype.resolveTiddler = function(tiddler)
{
  var t = (typeof tiddler == 'string') ? this.getTiddler(tiddler) : tiddler;
  return t instanceof Tiddler ? t : null;
};

// Filter a list of tiddlers
TiddlyWiki.prototype.filterTiddlers = function(filter)
{
  var results = [];
  if (filter) {
    var tiddler;
    var re = /([^\s\[\]]+)|(?:\[([ \w]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;
    var match = re.exec(filter);
    while (match) {
      if (match[1] || match[4]) {
        var title = match[1] || match[4];
        tiddler = this.fetchTiddler(title);
        if (tiddler) {
          results.pushUnique(tiddler);
        }
        else if (this.isShadowTiddler(title)) {
          tiddler = new Tiddler();
          tiddler.set(title, this.getTiddlerText(title));
          results.pushUnique(tiddler);
        }
      }
      else if (match[2]) {
        switch (match[2]) {
          case "tag":
            var matched = this.getTaggedTiddlers(match[3]);
            for (var m = 0; m < matched.length; m++)
              results.pushUnique(matched[m]);
            break;
          case "sort":
            results = this.sortTiddlers(results, match[3]);
            break;
        }
      }
      match = re.exec(filter);
    }
  }
  return results;
};

// Sort a list of tiddlers
TiddlyWiki.prototype.sortTiddlers = function(tiddlers, field)
{
  var asc = +1;
  switch (field.substr(0, 1)) {
    case "-":
      asc = -1;
    // Note: this fall-through is intentional
    /*jsl:fallthru*/
    case "+":
      field = field.substr(1);
      break;
  }
  if (TiddlyWiki.standardFieldAccess[field])
    tiddlers.sort(function(a, b) {return a[field] < b[field] ? -asc : (a[field] == b[field] ? 0 : asc);});
  else
    tiddlers.sort(function(a, b) {return a.fields[field] < b.fields[field] ? -asc : (a.fields[field] == b.fields[field] ? 0 : +asc);});
  return tiddlers;
};

// Returns true if path is a valid field name (path),
// i.e. a sequence of identifiers, separated by '.'
TiddlyWiki.isValidFieldName = function(name)
{
  var match = /[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*/.exec(name);
  return match && (match[0] == name);
};

// Throws an exception when name is not a valid field name.
TiddlyWiki.checkFieldName = function(name)
{
  if (!TiddlyWiki.isValidFieldName(name))
    throw config.messages.invalidFieldName.format([name]);
};

function StringFieldAccess(n, readOnly)
{
  this.set = readOnly ?
             function(t, v) {if (v != t[n]) throw config.messages.fieldCannotBeChanged.format([n]);} :
             function(t, v) {
               if (v != t[n]) {
                 t[n] = v;
                 return true;
               }
             };
  this.get = function(t) {return t[n];};
}

function DateFieldAccess(n)
{
  this.set = function(t, v) {
    var d = v instanceof Date ? v : Date.convertFromYYYYMMDDHHMM(v);
    if (d != t[n]) {
      t[n] = d;
      return true;
    }
  };
  this.get = function(t) {return t[n].convertToYYYYMMDDHHMM();};
}

function LinksFieldAccess(n)
{
  this.set = function(t, v) {
    var s = (typeof v == "string") ? v.readBracketedList() : v;
    if (s.toString() != t[n].toString()) {
      t[n] = s;
      return true;
    }
  };
  this.get = function(t) {return String.encodeTiddlyLinkList(t[n]);};
}

TiddlyWiki.standardFieldAccess = {
  // The set functions return true when setting the data has changed the value.
  "title":    new StringFieldAccess("title", true),
  // Handle the "tiddler" field name as the title
  "tiddler":  new StringFieldAccess("title", true),
  "text":     new StringFieldAccess("text"),
  "modifier": new StringFieldAccess("modifier"),
  "modified": new DateFieldAccess("modified"),
  "created":  new DateFieldAccess("created"),
  "tags":     new LinksFieldAccess("tags")
};

TiddlyWiki.isStandardField = function(name)
{
  return TiddlyWiki.standardFieldAccess[name] != undefined;
};

// Sets the value of the given field of the tiddler to the value.
// Setting an ExtendedField's value to null or undefined removes the field.
// Setting a namespace to undefined removes all fields of that namespace.
// The fieldName is case-insensitive.
// All values will be converted to a string value.
TiddlyWiki.prototype.setValue = function(tiddler, fieldName, value)
{
  TiddlyWiki.checkFieldName(fieldName);
  var t = this.resolveTiddler(tiddler);
  if (!t)
    return;
  fieldName = fieldName.toLowerCase();
  var isRemove = (value === undefined) || (value === null);
  var accessor = TiddlyWiki.standardFieldAccess[fieldName];
  if (accessor) {
    if (isRemove)
    // don't remove StandardFields
      return;
    var h = TiddlyWiki.standardFieldAccess[fieldName];
    if (!h.set(t, value))
      return;
  }
  else {
    var oldValue = t.fields[fieldName];
    if (isRemove) {
      if (oldValue !== undefined) {
        // deletes a single field
        delete t.fields[fieldName];
      }
      else {
        // no concrete value is defined for the fieldName
        // so we guess this is a namespace path.
        // delete all fields in a namespace
        var re = new RegExp('^' + fieldName + '\\.');
        var dirty = false;
        for (var n in t.fields) {
          if (n.match(re)) {
            delete t.fields[n];
            dirty = true;
          }
        }
        if (!dirty)
          return;
      }
    }
    else {
      // the "normal" set case. value is defined (not null/undefined)
      // For convenience provide a nicer conversion Date->String
      value = value instanceof Date ? value.convertToYYYYMMDDHHMMSSMMM() : String(value);
      if (oldValue == value)
        return;
      t.fields[fieldName] = value;
    }
  }
  // When we are here the tiddler/store really was changed.
  this.notify(t.title, true);
  if (!fieldName.match(/^temp\./))
    this.setDirty(true);
};

// Returns the value of the given field of the tiddler.
// The fieldName is case-insensitive.
// Will only return String values (or undefined).
TiddlyWiki.prototype.getValue = function(tiddler, fieldName)
{
  var t = this.resolveTiddler(tiddler);
  if (!t)
    return undefined;
  fieldName = fieldName.toLowerCase();
  var accessor = TiddlyWiki.standardFieldAccess[fieldName];
  if (accessor) {
    return accessor.get(t);
  }
  return t.fields[fieldName];
};

// Calls the callback function for every field in the tiddler.
// When callback function returns a non-false value the iteration stops
// and that value is returned.
// The order of the fields is not defined.
// @param callback a function(tiddler,fieldName,value).
TiddlyWiki.prototype.forEachField = function(tiddler, callback, onlyExtendedFields)
{
  var t = this.resolveTiddler(tiddler);
  if (!t)
    return undefined;
  var n,result;
  for (n in t.fields) {
    result = callback(t, n, t.fields[n]);
    if (result)
      return result;
  }
  if (onlyExtendedFields)
    return undefined;
  for (n in TiddlyWiki.standardFieldAccess) {
    if (n == "tiddler")
    // even though the "title" field can also be referenced through the name "tiddler"
    // we only visit this field once.
      continue;
    result = callback(t, n, TiddlyWiki.standardFieldAccess[n].get(t));
    if (result)
      return result;
  }
  return undefined;
};

//--
//-- Story functions
//--

function Story(containerId, idPrefix)
{
  this.container = containerId;
  this.idPrefix = idPrefix;
  this.highlightRegExp = null;
  this.tiddlerId = function(title) {
    var id = this.idPrefix + title;
    return id == this.container ? this.idPrefix + "_" + title : id;
  };
  this.containerId = function() {
    return this.container;
  };
}

Story.prototype.getTiddler = function(title)
{
  return document.getElementById(this.tiddlerId(title));
};

Story.prototype.getContainer = function()
{
  return document.getElementById(this.containerId());
};

Story.prototype.forEachTiddler = function(fn)
{
  var place = this.getContainer();
  if (!place)
    return;
  var e = place.firstChild;
  while (e) {
    var n = e.nextSibling;
    var title = e.getAttribute("tiddler");
    fn.call(this, title, e);
    e = n;
  }
};

Story.prototype.displayDefaultTiddlers = function()
{
  this.displayTiddlers(null, store.filterTiddlers(store.getTiddlerText("DefaultTiddlers")));
};

Story.prototype.displayTiddlers = function(srcElement, titles, template, animate, unused, customFields, toggle)
{
  for (var t = titles.length - 1; t >= 0; t--)
    this.displayTiddler(srcElement, titles[t], template, animate, unused, customFields);
};

Story.prototype.displayTiddler = function(srcElement, tiddler, template, animate, unused, customFields, toggle, animationSrc)
{
  var title = (tiddler instanceof Tiddler) ? tiddler.title : tiddler;
  var tiddlerElem = this.getTiddler(title);
  if (tiddlerElem) {
    if (toggle)
      this.closeTiddler(title, true);
    else
      this.refreshTiddler(title, template, false, customFields);
  }
  else {
    var place = this.getContainer();
    var before = this.positionTiddler(srcElement);
    tiddlerElem = this.createTiddler(place, before, title, template, customFields);
  }
  if (animationSrc && typeof animationSrc !== "string") {
    srcElement = animationSrc;
  }
  if (srcElement && typeof srcElement !== "string") {
    if (config.options.chkAnimate && (animate == undefined || animate == true) && anim && typeof Zoomer == "function" && typeof Scroller == "function")
      anim.startAnimating(new Zoomer(title, srcElement, tiddlerElem), new Scroller(tiddlerElem));
    else
      window.scrollTo(0, ensureVisible(tiddlerElem));
  }
};

Story.prototype.positionTiddler = function(srcElement)
{
  var place = this.getContainer();
  var before = null;
  if (typeof srcElement == "string") {
    switch (srcElement) {
      case "top":
        before = place.firstChild;
        break;
      case "bottom":
        before = null;
        break;
    }
  }
  else {
    var after = this.findContainingTiddler(srcElement);
    if (after == null) {
      before = place.firstChild;
    }
    else if (after.nextSibling) {
      before = after.nextSibling;
      if (before.nodeType != 1)
        before = null;
    }
  }
  return before;
};

Story.prototype.createTiddler = function(place, before, title, template, customFields)
{
  var tiddlerElem = createTiddlyElement(null, "div", this.tiddlerId(title), "tiddler");
  tiddlerElem.setAttribute("refresh", "tiddler");
  if (customFields)
    tiddlerElem.setAttribute("tiddlyFields", customFields);
  place.insertBefore(tiddlerElem, before);
  var defaultText = null;
  if (!store.tiddlerExists(title) && !store.isShadowTiddler(title))
    defaultText = this.loadMissingTiddler(title, customFields, tiddlerElem);
  this.refreshTiddler(title, template, false, customFields, defaultText);
  return tiddlerElem;
};

Story.prototype.loadMissingTiddler = function(title, fields, tiddlerElem)
{
  var tiddler = new Tiddler(title);
  tiddler.fields = typeof fields == "string" ? fields.decodeHashMap() : (fields || {});
  var serverType = tiddler.getServerType();
  var host = tiddler.fields['server.host'];
  var workspace = tiddler.fields['server.workspace'];
  if (!serverType || !host)
    return null;
  var sm = new SyncMachine(serverType, {
    start: function() {
      return this.openHost(host, "openWorkspace");
    },
    openWorkspace: function() {
      return this.openWorkspace(workspace, "getTiddler");
    },
    getTiddler: function() {
      return this.getTiddler(title, "onGetTiddler");
    },
    onGetTiddler: function(context) {
      var tiddler = context.tiddler;
      if (tiddler && tiddler.text) {
        var downloaded = new Date();
        if (!tiddler.created)
          tiddler.created = downloaded;
        if (!tiddler.modified)
          tiddler.modified = tiddler.created;
        store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
        autoSaveChanges();
      }
      delete this;
      return true;
    },
    error: function(message) {
      displayMessage("Error loading missing tiddler from %0: %1".format([host,message]));
    }
  });
  sm.go();
  return config.messages.loadingMissingTiddler.format([title,serverType,host,workspace]);
};

Story.prototype.chooseTemplateForTiddler = function(title, template)
{
  if (!template)
    template = DEFAULT_VIEW_TEMPLATE;
  if (template == DEFAULT_VIEW_TEMPLATE || template == DEFAULT_EDIT_TEMPLATE)
    template = config.tiddlerTemplates[template];
  return template;
};

Story.prototype.getTemplateForTiddler = function(title, template, tiddler)
{
  return store.getRecursiveTiddlerText(template, null, 10);
};

Story.prototype.refreshTiddler = function(title, template, force, customFields, defaultText)
{
  var tiddlerElem = this.getTiddler(title);
  if (tiddlerElem) {
    if (tiddlerElem.getAttribute("dirty") == "true" && !force)
      return tiddlerElem;
    template = this.chooseTemplateForTiddler(title, template);
    var currTemplate = tiddlerElem.getAttribute("template");
    if ((template != currTemplate) || force) {
      var tiddler = store.getTiddler(title);
      if (!tiddler) {
        tiddler = new Tiddler();
        if (store.isShadowTiddler(title)) {
          tiddler.set(title, store.getTiddlerText(title), config.views.wikified.shadowModifier, version.date, [], version.date);
        }
        else {
          var text = template == "EditTemplate" ?
                     config.views.editor.defaultText.format([title]) :
                     config.views.wikified.defaultText.format([title]);
          text = defaultText || text;
          var fields = customFields ? customFields.decodeHashMap() : null;
          tiddler.set(title, text, config.views.wikified.defaultModifier, version.date, [], version.date, fields);
        }
      }
      tiddlerElem.setAttribute("tags", tiddler.tags.join(" "));
      tiddlerElem.setAttribute("tiddler", title);
      tiddlerElem.setAttribute("template", template);
      tiddlerElem.onmouseover = this.onTiddlerMouseOver;
      tiddlerElem.onmouseout = this.onTiddlerMouseOut;
      tiddlerElem.ondblclick = this.onTiddlerDblClick;
      tiddlerElem[window.event ? "onkeydown" : "onkeypress"] = this.onTiddlerKeyPress;
      tiddlerElem.innerHTML = this.getTemplateForTiddler(title, template, tiddler);
      applyHtmlMacros(tiddlerElem, tiddler);
      if (store.getTaggedTiddlers(title).length > 0)
        addClass(tiddlerElem, "isTag");
      else
        removeClass(tiddlerElem, "isTag");
      if (store.tiddlerExists(title)) {
        removeClass(tiddlerElem, "shadow");
        removeClass(tiddlerElem, "missing");
      }
      else {
        addClass(tiddlerElem, store.isShadowTiddler(title) ? "shadow" : "missing");
      }
      if (customFields)
        this.addCustomFields(tiddlerElem, customFields);
      forceReflow();
    }
  }
  return tiddlerElem;
};

Story.prototype.addCustomFields = function(place, customFields)
{
  var fields = customFields.decodeHashMap();
  var w = document.createElement("div");
  w.style.display = "none";
  place.appendChild(w);
  for (var t in fields) {
    var e = document.createElement("input");
    e.setAttribute("type", "text");
    e.setAttribute("value", fields[t]);
    w.appendChild(e);
    e.setAttribute("edit", t);
  }
};

Story.prototype.refreshAllTiddlers = function(force)
{
  var e = this.getContainer().firstChild;
  while (e) {
    var template = e.getAttribute("template");
    if (template && e.getAttribute("dirty") != "true") {
      this.refreshTiddler(e.getAttribute("tiddler"), force ? null : template, true);
    }
    e = e.nextSibling;
  }
};

Story.prototype.onTiddlerMouseOver = function(e)
{
  if (window.addClass instanceof Function)
    addClass(this, "selected");
};

Story.prototype.onTiddlerMouseOut = function(e)
{
  if (window.removeClass instanceof Function)
    removeClass(this, "selected");
};

Story.prototype.onTiddlerDblClick = function(ev)
{
  var e = ev || window.event;
  var target = resolveTarget(e);
  if (target && target.nodeName.toLowerCase() != "input" && target.nodeName.toLowerCase() != "textarea") {
    if (document.selection && document.selection.empty)
      document.selection.empty();
    config.macros.toolbar.invokeCommand(this, "defaultCommand", e);
    e.cancelBubble = true;
    if (e.stopPropagation) e.stopPropagation();
    return true;
  }
  return false;
};

Story.prototype.onTiddlerKeyPress = function(ev)
{
  var e = ev || window.event;
  clearMessage();
  var consume = false;
  var title = this.getAttribute("tiddler");
  var target = resolveTarget(e);
  switch (e.keyCode) {
    case 9: // Tab
      if (config.options.chkInsertTabs && target.tagName.toLowerCase() == "textarea") {
        replaceSelection(target, String.fromCharCode(9));
        consume = true;
      }
      if (config.isOpera) {
        target.onblur = function() {
          this.focus();
          this.onblur = null;
        };
      }
      break;
    case 13: // Ctrl-Enter
    case 10: // Ctrl-Enter on IE PC
    case 77: // Ctrl-Enter is "M" on some platforms
      if (e.ctrlKey) {
        blurElement(this);
        config.macros.toolbar.invokeCommand(this, "defaultCommand", e);
        consume = true;
      }
      break;
    case 27: // Escape
      blurElement(this);
      config.macros.toolbar.invokeCommand(this, "cancelCommand", e);
      consume = true;
      break;
  }
  e.cancelBubble = consume;
  if (consume) {
    if (e.stopPropagation) e.stopPropagation(); // Stop Propagation
    e.returnValue = true; // Cancel The Event in IE
    if (e.preventDefault) e.preventDefault(); // Cancel The Event in Moz
  }
  return !consume;
};

Story.prototype.getTiddlerField = function(title, field)
{
  var tiddlerElem = this.getTiddler(title);
  var e = null;
  if (tiddlerElem) {
    var children = tiddlerElem.getElementsByTagName("*");
    for (var t = 0; t < children.length; t++) {
      var c = children[t];
      if (c.tagName.toLowerCase() == "input" || c.tagName.toLowerCase() == "textarea") {
        if (!e)
          e = c;
        if (c.getAttribute("edit") == field)
          e = c;
      }
    }
  }
  return e;
};

Story.prototype.focusTiddler = function(title, field)
{
  var e = this.getTiddlerField(title, field);
  if (e) {
    e.focus();
    e.select();
  }
};

Story.prototype.blurTiddler = function(title)
{
  var tiddlerElem = this.getTiddler(title);
  if (tiddlerElem && tiddlerElem.focus && tiddlerElem.blur) {
    tiddlerElem.focus();
    tiddlerElem.blur();
  }
};

Story.prototype.setTiddlerField = function(title, tag, mode, field)
{
  var c = this.getTiddlerField(title, field);
  var tags = c.value.readBracketedList();
  tags.setItem(tag, mode);
  c.value = String.encodeTiddlyLinkList(tags);
};

Story.prototype.setTiddlerTag = function(title, tag, mode)
{
  this.setTiddlerField(title, tag, mode, "tags");
};

Story.prototype.closeTiddler = function(title, animate, unused)
{
  var tiddlerElem = this.getTiddler(title);
  if (tiddlerElem) {
    clearMessage();
    this.scrubTiddler(tiddlerElem);
    if (config.options.chkAnimate && animate && anim && typeof Slider == "function")
      anim.startAnimating(new Slider(tiddlerElem, false, null, "all"));
    else {
      removeNode(tiddlerElem);
      forceReflow();
    }
  }
};

Story.prototype.scrubTiddler = function(tiddlerElem)
{
  tiddlerElem.id = null;
};

Story.prototype.setDirty = function(title, dirty)
{
  var tiddlerElem = this.getTiddler(title);
  if (tiddlerElem)
    tiddlerElem.setAttribute("dirty", dirty ? "true" : "false");
};

Story.prototype.isDirty = function(title)
{
  var tiddlerElem = this.getTiddler(title);
  if (tiddlerElem)
    return tiddlerElem.getAttribute("dirty") == "true";
  return null;
};

Story.prototype.areAnyDirty = function()
{
  var r = false;
  this.forEachTiddler(function(title, element) {
    if (this.isDirty(title))
      r = true;
  });
  return r;
};

Story.prototype.closeAllTiddlers = function(exclude)
{
  clearMessage();
  this.forEachTiddler(function(title, element) {
    if ((title != exclude) && element.getAttribute("dirty") != "true")
      this.closeTiddler(title);
  });
  window.scrollTo(0, ensureVisible(this.container));
};

Story.prototype.isEmpty = function()
{
  var place = this.getContainer();
  return place && place.firstChild == null;
};

Story.prototype.search = function(text, useCaseSensitive, useRegExp)
{
  this.closeAllTiddlers();
  highlightHack = new RegExp(useRegExp ? text : text.escapeRegExp(), useCaseSensitive ? "mg" : "img");
  var matches = store.search(highlightHack, "title", "excludeSearch");
  this.displayTiddlers(null, matches);
  highlightHack = null;
  var q = useRegExp ? "/" : "'";
  if (matches.length > 0)
    displayMessage(config.macros.search.successMsg.format([matches.length.toString(),q + text + q]));
  else
    displayMessage(config.macros.search.failureMsg.format([q + text + q]));
};

Story.prototype.findContainingTiddler = function(e)
{
  while (e && !hasClass(e, "tiddler"))
    e = e.parentNode;
  return e;
};

Story.prototype.gatherSaveFields = function(e, fields)
{
  if (e && e.getAttribute) {
    var f = e.getAttribute("edit");
    if (f)
      fields[f] = e.value.replace(/\r/mg, "");
    if (e.hasChildNodes()) {
      var c = e.childNodes;
      for (var t = 0; t < c.length; t++)
        this.gatherSaveFields(c[t], fields);
    }
  }
};

Story.prototype.hasChanges = function(title)
{
  var e = this.getTiddler(title);
  if (e) {
    var fields = {};
    this.gatherSaveFields(e, fields);
    var tiddler = store.fetchTiddler(title);
    if (!tiddler)
      return false;
    for (var n in fields) {
      if (store.getValue(title, n) != fields[n])
        return true;
    }
  }
  return false;
};

Story.prototype.saveTiddler = function(title, minorUpdate)
{
  var tiddlerElem = this.getTiddler(title);
  if (tiddlerElem) {
    var fields = {};
    this.gatherSaveFields(tiddlerElem, fields);
    var newTitle = fields.title || title;
    if (!store.tiddlerExists(newTitle))
      newTitle = newTitle.trim();
    if (store.tiddlerExists(newTitle) && newTitle != title) {
      if (!confirm(config.messages.overwriteWarning.format([newTitle.toString()])))
        return null;
    }
    if (newTitle != title)
      this.closeTiddler(newTitle, false);
    tiddlerElem.id = this.tiddlerId(newTitle);
    tiddlerElem.setAttribute("tiddler", newTitle);
    tiddlerElem.setAttribute("template", DEFAULT_VIEW_TEMPLATE);
    tiddlerElem.setAttribute("dirty", "false");
    if (config.options.chkForceMinorUpdate)
      minorUpdate = !minorUpdate;
    if (!store.tiddlerExists(newTitle))
      minorUpdate = false;
    var newDate = new Date();
    var extendedFields = store.tiddlerExists(newTitle) ? store.fetchTiddler(newTitle).fields : (newTitle != title && store.tiddlerExists(title) ? store.fetchTiddler(title).fields : config.defaultCustomFields);
    for (var n in fields) {
      if (!TiddlyWiki.isStandardField(n))
        extendedFields[n] = fields[n];
    }
    var tiddler = store.saveTiddler(title, newTitle, fields.text, minorUpdate ? undefined : config.options.txtUserName, minorUpdate ? undefined : newDate, fields.tags, extendedFields);
    autoSaveChanges(null, [tiddler]);
    return newTitle;
  }
  return null;
};

Story.prototype.permaView = function()
{
  var links = [];
  this.forEachTiddler(function(title, element) {
    links.push(String.encodeTiddlyLink(title));
  });
  var t = encodeURIComponent(links.join(" "));
  if (t == "")
    t = "#";
  if (window.location.hash != t)
    window.location.hash = t;
};

Story.prototype.switchTheme = function(theme)
{
  if (safeMode)
    return;

  var isAvailable = function(title) {
    var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
    if (s != -1)
      title = title.substr(0, s);
    return store.tiddlerExists(title) || store.isShadowTiddler(title);
  };

  var getSlice = function(theme, slice) {
    var r;
    if (readOnly)
      r = store.getTiddlerSlice(theme, slice + "ReadOnly") || store.getTiddlerSlice(theme, "Web" + slice);
    r = r || store.getTiddlerSlice(theme, slice);
    if (r && r.indexOf(config.textPrimitives.sectionSeparator) == 0)
      r = theme + r;
    return isAvailable(r) ? r : slice;
  };

  var replaceNotification = function(i, name, theme, slice) {
    var newName = getSlice(theme, slice);
    if (name != newName && store.namedNotifications[i].name == name) {
      store.namedNotifications[i].name = newName;
      return newName;
    }
    return name;
  };

  var pt = config.refresherData.pageTemplate;
  var vi = DEFAULT_VIEW_TEMPLATE;
  var vt = config.tiddlerTemplates[vi];
  var ei = DEFAULT_EDIT_TEMPLATE;
  var et = config.tiddlerTemplates[ei];

  for (var i = 0; i < config.notifyTiddlers.length; i++) {
    var name = config.notifyTiddlers[i].name;
    switch (name) {
      case "PageTemplate":
        config.refresherData.pageTemplate = replaceNotification(i, config.refresherData.pageTemplate, theme, name);
        break;
      case "StyleSheet":
        removeStyleSheet(config.refresherData.styleSheet);
        config.refresherData.styleSheet = replaceNotification(i, config.refresherData.styleSheet, theme, name);
        break;
      case "ColorPalette":
        config.refresherData.colorPalette = replaceNotification(i, config.refresherData.colorPalette, theme, name);
        break;
      default:
        break;
    }
  }
  config.tiddlerTemplates[vi] = getSlice(theme, "ViewTemplate");
  config.tiddlerTemplates[ei] = getSlice(theme, "EditTemplate");
  if (!startingUp) {
    if (config.refresherData.pageTemplate != pt || config.tiddlerTemplates[vi] != vt || config.tiddlerTemplates[ei] != et) {
      refreshAll();
      this.refreshAllTiddlers(true);
    }
    else {
      setStylesheet(store.getRecursiveTiddlerText(config.refresherData.styleSheet, "", 10), config.refreshers.styleSheet);
    }
    config.options.txtTheme = theme;
    saveOptionCookie("txtTheme");
  }
};

//--
//-- Backstage
//--

var backstage = {
  area: null,
  toolbar: null,
  button: null,
  showButton: null,
  hideButton: null,
  cloak: null,
  panel: null,
  panelBody: null,
  panelFooter: null,
  currTabName: null,
  currTabElem: null,
  content: null,

  init: function() {
    var cmb = config.messages.backstage;
    this.area = document.getElementById("backstageArea");
    this.toolbar = document.getElementById("backstageToolbar");
    this.button = document.getElementById("backstageButton");
    this.button.style.display = "block";
    var t = cmb.open.text + " " + glyph("bentArrowLeft");
    this.showButton = createTiddlyButton(this.button, t, cmb.open.tooltip,
            function(e) {
              backstage.show();
              return false;
            }, null, "backstageShow");
    t = glyph("bentArrowRight") + " " + cmb.close.text;
    this.hideButton = createTiddlyButton(this.button, t, cmb.close.tooltip,
            function(e) {
              backstage.hide();
              return false;
            }, null, "backstageHide");
    this.cloak = document.getElementById("backstageCloak");
    this.panel = document.getElementById("backstagePanel");
    this.panelFooter = createTiddlyElement(this.panel, "div", null, "backstagePanelFooter");
    this.panelBody = createTiddlyElement(this.panel, "div", null, "backstagePanelBody");
    this.cloak.onmousedown = function(e) {backstage.switchTab(null);};
    createTiddlyText(this.toolbar, cmb.prompt);
    for (t = 0; t < config.backstageTasks.length; t++) {
      var taskName = config.backstageTasks[t];
      var task = config.tasks[taskName];
      var handler = task.action ? this.onClickCommand : this.onClickTab;
      var text = task.text + (task.action ? "" : glyph("downTriangle"));
      var btn = createTiddlyButton(this.toolbar, text, task.tooltip, handler, "backstageTab");
      btn.setAttribute("task", taskName);
      addClass(btn, task.action ? "backstageAction" : "backstageTask");
    }
    this.content = document.getElementById("contentWrapper");
    if (config.options.chkBackstage)
      this.show();
    else
      this.hide();
  },

  isVisible: function() {
    return this.area ? this.area.style.display == "block" : false;
  },

  show: function() {
    this.area.style.display = "block";
    if (anim && config.options.chkAnimate) {
      backstage.toolbar.style.left = findWindowWidth() + "px";
      var p = [
        {
          style: "left",
          start: findWindowWidth(),
          end: 0,
          template: "%0px"
        }
      ];
      anim.startAnimating(new Morpher(backstage.toolbar, config.animDuration, p));
    }
    else {
      backstage.area.style.left = "0px";
    }
    this.showButton.style.display = "none";
    this.hideButton.style.display = "block";
    config.options.chkBackstage = true;
    saveOptionCookie("chkBackstage");
    addClass(this.content, "backstageVisible");
  },

  hide: function() {
    if (this.currTabElem) {
      this.switchTab(null);
    }
    else {
      backstage.toolbar.style.left = "0px";
      if (anim && config.options.chkAnimate) {
        var p = [
          {
            style: "left",
            start: 0,
            end: findWindowWidth(),
            template: "%0px"
          }
        ];
        var c = function(element, properties) {backstage.area.style.display = "none";};
        anim.startAnimating(new Morpher(backstage.toolbar, config.animDuration, p, c));
      }
      else {
        this.area.style.display = "none";
      }
      this.showButton.style.display = "block";
      this.hideButton.style.display = "none";
      config.options.chkBackstage = false;
      saveOptionCookie("chkBackstage");
      removeClass(this.content, "backstageVisible");
    }
  },

  onClickCommand: function(e) {
    var task = config.tasks[this.getAttribute("task")];
    displayMessage(task);
    if (task.action) {
      backstage.switchTab(null);
      task.action();
    }
    return false;
  },

  onClickTab: function(e) {
    backstage.switchTab(this.getAttribute("task"));
    return false;
  },

  // Switch to a given tab, or none if null is passed
  switchTab: function(tabName) {
    var tabElem = null;
    var e = this.toolbar.firstChild;
    while (e)
    {
      if (e.getAttribute && e.getAttribute("task") == tabName)
        tabElem = e;
      e = e.nextSibling;
    }
    if (tabName == backstage.currTabName)
      return;
    if (backstage.currTabElem) {
      removeClass(this.currTabElem, "backstageSelTab");
    }
    if (tabElem && tabName) {
      backstage.preparePanel();
      addClass(tabElem, "backstageSelTab");
      var task = config.tasks[tabName];
      wikify(task.content, backstage.panelBody, null, null);
      backstage.showPanel();
    }
    else if (backstage.currTabElem) {
      backstage.hidePanel();
    }
    backstage.currTabName = tabName;
    backstage.currTabElem = tabElem;
  },

  isPanelVisible: function() {
    return backstage.panel ? backstage.panel.style.display == "block" : false;
  },

  preparePanel: function() {
    backstage.cloak.style.height = findWindowHeight() + "px";
    backstage.cloak.style.display = "block";
    removeChildren(backstage.panelBody);
    return backstage.panelBody;
  },

  showPanel: function() {
    backstage.panel.style.display = "block";
    if (anim && config.options.chkAnimate) {
      backstage.panel.style.top = (-backstage.panel.offsetHeight) + "px";
      var p = [
        {
          style: "top",
          start: -backstage.panel.offsetHeight,
          end: 0,
          template: "%0px"
        }
      ];
      anim.startAnimating(new Morpher(backstage.panel, config.animDuration, p), new Scroller(backstage.panel, false));
    }
    else {
      backstage.panel.style.top = "0px";
    }
    return backstage.panelBody;
  },

  hidePanel: function() {
    if (backstage.currTabElem)
      removeClass(backstage.currTabElem, "backstageSelTab");
    backstage.currTabElem = null;
    backstage.currTabName = null;
    if (anim && config.options.chkAnimate) {
      var p = [
        {
          style: "top",
          start: 0,
          end: -(backstage.panel.offsetHeight),
          template: "%0px"
        },
        {
          style: "display",
          atEnd: "none"
        }
      ];
      var c = function(element, properties) {backstage.cloak.style.display = "none";};
      anim.startAnimating(new Morpher(backstage.panel, config.animDuration, p, c));
    }
    else {
      backstage.panel.style.display = "none";
      backstage.cloak.style.display = "none";
    }
  }
};

config.macros.backstage = {};

config.macros.backstage.handler = function(place, macroName, params)
{
  var backstageTask = config.tasks[params[0]];
  if (backstageTask)
    createTiddlyButton(place, backstageTask.text, backstageTask.tooltip, function(e) {
      backstage.switchTab(params[0]);
      return false;
    });
};

//--
//-- ImportTiddlers macro
//--

config.macros.importTiddlers.handler = function(place, macroName, params, wikifier, paramString, tiddler)
{
  if (readOnly) {
    createTiddlyElement(place, "div", null, "marked", this.readOnlyWarning);
    return;
  }
  var w = new Wizard();
  w.createWizard(place, this.wizardTitle);
  this.restart(w);
};

config.macros.importTiddlers.onCancel = function(e)
{
  var wizard = new Wizard(this);
  var place = wizard.clear();
  config.macros.importTiddlers.restart(wizard);
  return false;
};

config.macros.importTiddlers.onClose = function(e)
{
  backstage.hidePanel();
  return false;
};

config.macros.importTiddlers.restart = function(wizard)
{
  wizard.addStep(this.step1Title, this.step1Html);
  var s = wizard.getElement("selTypes");
  for (var t in config.adaptors) {
    var e = createTiddlyElement(s, "option", null, null, config.adaptors[t].serverLabel ? config.adaptors[t].serverLabel : t);
    e.value = t;
  }
  if (config.defaultAdaptor)
    s.value = config.defaultAdaptor;
  s = wizard.getElement("selFeeds");
  var feeds = this.getFeeds();
  for (t in feeds) {
    e = createTiddlyElement(s, "option", null, null, t);
    e.value = t;
  }
  wizard.setValue("feeds", feeds);
  s.onchange = config.macros.importTiddlers.onFeedChange;
  var fileInput = wizard.getElement("txtBrowse");
  fileInput.onchange = config.macros.importTiddlers.onBrowseChange;
  fileInput.onkeyup = config.macros.importTiddlers.onBrowseChange;
  wizard.setButtons([
    {
      caption: this.openLabel,
      tooltip: this.openPrompt,
      onClick: config.macros.importTiddlers.onOpen
    }
  ]);
  wizard.formElem.action = "javascript:;";
  wizard.formElem.onsubmit = function() {
    if (this.txtPath.value.length)
      this.lastChild.firstChild.onclick();
  };
};

config.macros.importTiddlers.getFeeds = function()
{
  var feeds = {};
  var tagged = store.getTaggedTiddlers("systemServer", "title");
  for (var t = 0; t < tagged.length; t++) {
    var title = tagged[t].title;
    var serverType = store.getTiddlerSlice(title, "Type");
    if (!serverType)
      serverType = "file";
    feeds[title] = {title: title,
      url: store.getTiddlerSlice(title, "URL"),
      workspace: store.getTiddlerSlice(title, "Workspace"),
      workspaceList: store.getTiddlerSlice(title, "WorkspaceList"),
      tiddlerFilter: store.getTiddlerSlice(title, "TiddlerFilter"),
      serverType: serverType,
      description: store.getTiddlerSlice(title, "Description")};
  }
  return feeds;
};

config.macros.importTiddlers.onFeedChange = function(e)
{
  var wizard = new Wizard(this);
  var selTypes = wizard.getElement("selTypes");
  var fileInput = wizard.getElement("txtPath");
  var feeds = wizard.getValue("feeds");
  var f = feeds[this.value];
  if (f) {
    selTypes.value = f.serverType;
    fileInput.value = f.url;
    wizard.setValue("feedName", f.serverType);
    wizard.setValue("feedHost", f.url);
    wizard.setValue("feedWorkspace", f.workspace);
    wizard.setValue("feedWorkspaceList", f.workspaceList);
    wizard.setValue("feedTiddlerFilter", f.tiddlerFilter);
  }
  return false;
};

config.macros.importTiddlers.onBrowseChange = function(e)
{
  var wizard = new Wizard(this);
  var fileInput = wizard.getElement("txtPath");
  fileInput.value = config.macros.importTiddlers.getURLFromLocalPath(this.value);
  var serverType = wizard.getElement("selTypes");
  serverType.value = "file";
  return true;
};

config.macros.importTiddlers.getURLFromLocalPath = function(v)
{
  if (!v || !v.length)
    return v;
  v = v.replace(/\\/g, "/"); // use "/" for cross-platform consistency
  var u;
  var t = v.split(":");
  var p = t[1] || t[0]; // remove drive letter (if any)
  if (t[1] && (t[0] == "http" || t[0] == "https" || t[0] == "file")) {
    u = v;
  }
  else if (p.substr(0, 1) == "/") {
    u = document.location.protocol + "//" + document.location.hostname + (t[1] ? "/" : "") + v;
  }
  else {
    var c = document.location.href.replace(/\\/g, "/");
    var pos = c.lastIndexOf("/");
    if (pos != -1)
      c = c.substr(0, pos); // remove filename
    u = c + "/" + p;
  }
  return u;
};

config.macros.importTiddlers.onOpen = function(e)
{
  var wizard = new Wizard(this);
  var fileInput = wizard.getElement("txtPath");
  var url = fileInput.value;
  var serverType = wizard.getElement("selTypes").value || config.defaultAdaptor;
  var adaptor = new config.adaptors[serverType]();
  wizard.setValue("adaptor", adaptor);
  wizard.setValue("serverType", serverType);
  wizard.setValue("host", url);
  var ret = adaptor.openHost(url, null, wizard, config.macros.importTiddlers.onOpenHost);
  if (ret !== true)
    displayMessage(ret);
  wizard.setButtons([
    {
      caption: config.macros.importTiddlers.cancelLabel,
      tooltip: config.macros.importTiddlers.cancelPrompt,
      onClick: config.macros.importTiddlers.onCancel
    }
  ], config.macros.importTiddlers.statusOpenHost);
  return false;
};

config.macros.importTiddlers.onOpenHost = function(context, wizard)
{
  var adaptor = wizard.getValue("adaptor");
  if (context.status !== true)
    displayMessage("Error in importTiddlers.onOpenHost: " + context.statusText);
  var ret = adaptor.getWorkspaceList(context, wizard, config.macros.importTiddlers.onGetWorkspaceList);
  if (ret !== true)
    displayMessage(ret);
  wizard.setButtons([
    {
      caption: config.macros.importTiddlers.cancelLabel,
      tooltip: config.macros.importTiddlers.cancelPrompt,
      onClick: config.macros.importTiddlers.onCancel
    }
  ], config.macros.importTiddlers.statusGetWorkspaceList);
};

config.macros.importTiddlers.onGetWorkspaceList = function(context, wizard)
{
  if (context.status !== true)
    displayMessage("Error in importTiddlers.onGetWorkspaceList: " + context.statusText);
  wizard.setValue("context", context);
  var workspace = wizard.getValue("feedWorkspace");
  if (!workspace && context.workspaces.length == 1)
    workspace = context.workspaces[0].title;
  if (workspace) {
    var ret = context.adaptor.openWorkspace(workspace, context, wizard, config.macros.importTiddlers.onOpenWorkspace);
    if (ret !== true)
      displayMessage(ret);
    wizard.setValue("workspace", workspace);
    wizard.setButtons([
      {
        caption: config.macros.importTiddlers.cancelLabel,
        tooltip: config.macros.importTiddlers.cancelPrompt,
        onClick: config.macros.importTiddlers.onCancel
      }
    ], config.macros.importTiddlers.statusOpenWorkspace);
    return;
  }
  wizard.addStep(config.macros.importTiddlers.step2Title, config.macros.importTiddlers.step2Html);
  var s = wizard.getElement("selWorkspace");
  s.onchange = config.macros.importTiddlers.onWorkspaceChange;
  for (var t = 0; t < context.workspaces.length; t++) {
    var e = createTiddlyElement(s, "option", null, null, context.workspaces[t].title);
    e.value = context.workspaces[t].title;
  }
  var workspaceList = wizard.getValue("feedWorkspaceList");
  if (workspaceList) {
    var list = workspaceList.parseParams("workspace", null, false, true);
    for (var n = 1; n < list.length; n++) {
      if (context.workspaces.findByField("title", list[n].value) == null) {
        e = createTiddlyElement(s, "option", null, null, list[n].value);
        e.value = list[n].value;
      }
    }
  }
  if (workspace) {
    t = wizard.getElement("txtWorkspace");
    t.value = workspace;
  }
  wizard.setButtons([
    {
      caption: config.macros.importTiddlers.openLabel,
      tooltip: config.macros.importTiddlers.openPrompt,
      onClick: config.macros.importTiddlers.onChooseWorkspace
    }
  ]);
};

config.macros.importTiddlers.onWorkspaceChange = function(e)
{
  var wizard = new Wizard(this);
  var t = wizard.getElement("txtWorkspace");
  t.value = this.value;
  this.selectedIndex = 0;
  return false;
};

config.macros.importTiddlers.onChooseWorkspace = function(e)
{
  var wizard = new Wizard(this);
  var adaptor = wizard.getValue("adaptor");
  var workspace = wizard.getElement("txtWorkspace").value;
  wizard.setValue("workspace", workspace);
  var context = wizard.getValue("context");
  var ret = adaptor.openWorkspace(workspace, context, wizard, config.macros.importTiddlers.onOpenWorkspace);
  if (ret !== true)
    displayMessage(ret);
  wizard.setButtons([
    {
      caption: config.macros.importTiddlers.cancelLabel,
      tooltip: config.macros.importTiddlers.cancelPrompt,
      onClick: config.macros.importTiddlers.onCancel
    }
  ], config.macros.importTiddlers.statusOpenWorkspace);
  return false;
};

config.macros.importTiddlers.onOpenWorkspace = function(context, wizard)
{
  if (context.status !== true)
    displayMessage("Error in importTiddlers.onOpenWorkspace: " + context.statusText);
  var adaptor = wizard.getValue("adaptor");
  var ret = adaptor.getTiddlerList(context, wizard, config.macros.importTiddlers.onGetTiddlerList, wizard.getValue("feedTiddlerFilter"));
  if (ret !== true)
    displayMessage(ret);
  wizard.setButtons([
    {
      caption: config.macros.importTiddlers.cancelLabel,
      tooltip: config.macros.importTiddlers.cancelPrompt,
      onClick: config.macros.importTiddlers.onCancel
    }
  ], config.macros.importTiddlers.statusGetTiddlerList);
};

config.macros.importTiddlers.onGetTiddlerList = function(context, wizard)
{
  if (context.status !== true) {
    wizard.setButtons([
      {
        caption: config.macros.importTiddlers.cancelLabel,
        tooltip: config.macros.importTiddlers.cancelPrompt,
        onClick: config.macros.importTiddlers.onCancel
      }
    ], config.macros.importTiddlers.errorGettingTiddlerList);
    return;
  }
  // Extract data for the listview
  var listedTiddlers = [];
  if (context.tiddlers) {
    for (var n = 0; n < context.tiddlers.length; n++) {
      var tiddler = context.tiddlers[n];
      listedTiddlers.push({
        title: tiddler.title,
        modified: tiddler.modified,
        modifier: tiddler.modifier,
        text: tiddler.text ? wikifyPlainText(tiddler.text, 100) : "",
        tags: tiddler.tags,
        size: tiddler.text ? tiddler.text.length : 0,
        tiddler: tiddler
      });
    }
  }
  listedTiddlers.sort(function(a, b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
  // Display the listview
  wizard.addStep(config.macros.importTiddlers.step3Title, config.macros.importTiddlers.step3Html);
  var markList = wizard.getElement("markList");
  var listWrapper = document.createElement("div");
  markList.parentNode.insertBefore(listWrapper, markList);
  var listView = ListView.create(listWrapper, listedTiddlers, config.macros.importTiddlers.listViewTemplate);
  wizard.setValue("listView", listView);
  var txtSaveTiddler = wizard.getElement("txtSaveTiddler");
  txtSaveTiddler.value = config.macros.importTiddlers.generateSystemServerName(wizard);
  wizard.setButtons([
    {
      caption: config.macros.importTiddlers.cancelLabel,
      tooltip: config.macros.importTiddlers.cancelPrompt,
      onClick: config.macros.importTiddlers.onCancel
    },
    {
      caption: config.macros.importTiddlers.importLabel,
      tooltip: config.macros.importTiddlers.importPrompt,
      onClick: config.macros.importTiddlers.doImport
    }
  ]);
};

config.macros.importTiddlers.generateSystemServerName = function(wizard)
{
  var serverType = wizard.getValue("serverType");
  var host = wizard.getValue("host");
  var workspace = wizard.getValue("workspace");
  var pattern = config.macros.importTiddlers[workspace ? "systemServerNamePattern" : "systemServerNamePatternNoWorkspace"];
  return pattern.format([serverType,host,workspace]);
};

config.macros.importTiddlers.saveServerTiddler = function(wizard)
{
  var txtSaveTiddler = wizard.getElement("txtSaveTiddler").value;
  if (store.tiddlerExists(txtSaveTiddler)) {
    if (!confirm(config.macros.importTiddlers.confirmOverwriteSaveTiddler.format([txtSaveTiddler])))
      return;
    store.suspendNotifications();
    store.removeTiddler(txtSaveTiddler);
    store.resumeNotifications();
  }
  var serverType = wizard.getValue("serverType");
  var host = wizard.getValue("host");
  var workspace = wizard.getValue("workspace");
  var text = config.macros.importTiddlers.serverSaveTemplate.format([serverType,host,workspace]);
  store.saveTiddler(txtSaveTiddler, txtSaveTiddler, text, config.macros.importTiddlers.serverSaveModifier, new Date(), ["systemServer"]);
};

config.macros.importTiddlers.doImport = function(e)
{
  var wizard = new Wizard(this);
  if (wizard.getElement("chkSave").checked)
    config.macros.importTiddlers.saveServerTiddler(wizard);
  var chkSync = wizard.getElement("chkSync").checked;
  wizard.setValue("sync", chkSync);
  var listView = wizard.getValue("listView");
  var rowNames = ListView.getSelectedRows(listView);
  var adaptor = wizard.getValue("adaptor");
  var overwrite = [];
  var t;
  for (t = 0; t < rowNames.length; t++) {
    if (store.tiddlerExists(rowNames[t]))
      overwrite.push(rowNames[t]);
  }
  if (overwrite.length > 0) {
    if (!confirm(config.macros.importTiddlers.confirmOverwriteText.format([overwrite.join(", ")])))
      return false;
  }
  wizard.addStep(config.macros.importTiddlers.step4Title.format([rowNames.length]), config.macros.importTiddlers.step4Html);
  for (t = 0; t < rowNames.length; t++) {
    var link = document.createElement("div");
    createTiddlyLink(link, rowNames[t], true);
    var place = wizard.getElement("markReport");
    place.parentNode.insertBefore(link, place);
  }
  wizard.setValue("remainingImports", rowNames.length);
  wizard.setButtons([
    {
      caption: config.macros.importTiddlers.cancelLabel,
      tooltip: config.macros.importTiddlers.cancelPrompt,
      onClick: config.macros.importTiddlers.onCancel
    }
  ], config.macros.importTiddlers.statusDoingImport);
  for (t = 0; t < rowNames.length; t++) {
    var context = {};
    context.allowSynchronous = true;
    var inbound = adaptor.getTiddler(rowNames[t], context, wizard, config.macros.importTiddlers.onGetTiddler);
  }
  return false;
};

config.macros.importTiddlers.onGetTiddler = function(context, wizard)
{
  if (!context.status)
    displayMessage("Error in importTiddlers.onGetTiddler: " + context.statusText);
  var tiddler = context.tiddler;
  store.suspendNotifications();
  store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
  if (!wizard.getValue("sync")) {
    store.setValue(tiddler.title, 'server', null);
  }
  store.resumeNotifications();
  if (!context.isSynchronous)
    store.notify(tiddler.title, true);
  var remainingImports = wizard.getValue("remainingImports") - 1;
  wizard.setValue("remainingImports", remainingImports);
  if (remainingImports == 0) {
    if (context.isSynchronous) {
      store.notifyAll();
      refreshDisplay();
    }
    wizard.setButtons([
      {
        caption: config.macros.importTiddlers.doneLabel,
        tooltip: config.macros.importTiddlers.donePrompt,
        onClick: config.macros.importTiddlers.onClose
      }
    ], config.macros.importTiddlers.statusDoneImport);
    autoSaveChanges();
  }
};

//--
//-- Upgrade macro
//--

config.macros.upgrade.handler = function(place)
{
  var w = new Wizard();
  w.createWizard(place, this.wizardTitle);
  w.addStep(this.step1Title, this.step1Html.format([this.source,this.source]));
  w.setButtons([
    {
      caption: this.upgradeLabel,
      tooltip: this.upgradePrompt,
      onClick: this.onClickUpgrade
    }
  ]);
};

config.macros.upgrade.onClickUpgrade = function(e)
{
  var me = config.macros.upgrade;
  var w = new Wizard(this);
  if (window.location.protocol != "file:") {
    alert(me.errorCantUpgrade);
    return false;
  }
  if (story.areAnyDirty() || store.isDirty()) {
    alert(me.errorNotSaved);
    return false;
  }
  var localPath = getLocalPath(document.location.toString());
  var backupPath = getBackupPath(localPath, me.backupExtension);
  w.setValue("backupPath", backupPath);
  w.setButtons([], me.statusPreparingBackup);
  var original = loadOriginal(localPath);
  w.setButtons([], me.statusSavingBackup);
  var backup = config.browser.isIE ? ieCopyFile(backupPath, localPath) : saveFile(backupPath, original);
  if (backup != true) {
    w.setButtons([], me.errorSavingBackup);
    alert(me.errorSavingBackup);
    return false;
  }
  w.setButtons([], me.statusLoadingCore);
  var load = loadRemoteFile(me.source, me.onLoadCore, w);
  if (typeof load == "string") {
    w.setButtons([], me.errorLoadingCore);
    alert(me.errorLoadingCore);
    return false;
  }
  return false;
};

config.macros.upgrade.onLoadCore = function(status, params, responseText, url, xhr)
{
  var me = config.macros.upgrade;
  var w = params;
  var errMsg;
  if (!status)
    errMsg = me.errorLoadingCore;
  var newVer = me.extractVersion(responseText);
  if (!newVer)
    errMsg = me.errorCoreFormat;
  if (errMsg) {
    w.setButtons([], errMsg);
    alert(errMsg);
    return;
  }
  var onStartUpgrade = function(e) {
    w.setButtons([], me.statusSavingCore);
    var localPath = getLocalPath(document.location.toString());
    saveFile(localPath, responseText);
    w.setButtons([], me.statusReloadingCore);
    var backupPath = w.getValue("backupPath");
    var newLoc = document.location.toString() + '?time=' + new Date().convertToYYYYMMDDHHMM() + '#upgrade:[[' + encodeURI(backupPath) + ']]';
    window.setTimeout(function () {window.location = newLoc;}, 10);
  };
  var step2 = [me.step2Html_downgrade,me.step2Html_restore,me.step2Html_upgrade][compareVersions(version, newVer) + 1];
  w.addStep(me.step2Title, step2.format([formatVersion(newVer),formatVersion(version)]));
  w.setButtons([
    {
      caption: me.startLabel,
      tooltip: me.startPrompt,
      onClick: onStartUpgrade
    },
    {
      caption: me.cancelLabel,
      tooltip: me.cancelPrompt,
      onClick: me.onCancel
    }
  ]);
};

config.macros.upgrade.onCancel = function(e)
{
  var me = config.macros.upgrade;
  var w = new Wizard(this);
  w.addStep(me.step3Title, me.step3Html);
  w.setButtons([]);
  return false;
};

config.macros.upgrade.extractVersion = function(upgradeFile)
{
  var re = /^var version = \{title: "([^"]+)", major: (\d+), minor: (\d+), revision: (\d+)(, beta: (\d+)){0,1}, date: new Date\("([^"]+)"\)/mg;
  var m = re.exec(upgradeFile);
  return  m ? {title: m[1], major: m[2], minor: m[3], revision: m[4], beta: m[6], date: new Date(m[7])} : null;
};

function upgradeFrom(path)
{
  var importStore = new TiddlyWiki();
  var tw = loadFile(path);
  if (window.netscape !== undefined)
    tw = convertUTF8ToUnicode(tw);
  importStore.importTiddlyWiki(tw);
  importStore.forEachTiddler(function(title, tiddler) {
    if (!store.getTiddler(title)) {
      store.addTiddler(tiddler);
    }
  });
  refreshDisplay();
  saveChanges(); //# To create appropriate Markup* sections
  alert(config.messages.upgradeDone.format([formatVersion()]));
  window.location = window.location.toString().substr(0, window.location.toString().lastIndexOf('?'));
}

//--
//-- Sync macro
//--

// Synchronisation handlers
config.syncers = {};

// Sync state.
var currSync = null;

// sync macro
config.macros.sync.handler = function(place, macroName, params, wikifier, paramString, tiddler)
{
  if (!wikifier.isStatic)
    this.startSync(place);
};

config.macros.sync.cancelSync = function()
{
  currSync = null;
};

config.macros.sync.startSync = function(place)
{
  if (currSync)
    config.macros.sync.cancelSync();
  currSync = {};
  currSync.syncList = this.getSyncableTiddlers();
  currSync.syncTasks = this.createSyncTasks(currSync.syncList);
  this.preProcessSyncableTiddlers(currSync.syncList);
  var wizard = new Wizard();
  currSync.wizard = wizard;
  wizard.createWizard(place, this.wizardTitle);
  wizard.addStep(this.step1Title, this.step1Html);
  var markList = wizard.getElement("markList");
  var listWrapper = document.createElement("div");
  markList.parentNode.insertBefore(listWrapper, markList);
  currSync.listView = ListView.create(listWrapper, currSync.syncList, this.listViewTemplate);
  this.processSyncableTiddlers(currSync.syncList);
  wizard.setButtons([
    {
      caption: this.syncLabel,
      tooltip: this.syncPrompt,
      onClick: this.doSync
    }
  ]);
};

config.macros.sync.getSyncableTiddlers = function()
{
  var list = [];
  store.forEachTiddler(function(title, tiddler) {
    var syncItem = {};
    syncItem.serverType = tiddler.getServerType();
    syncItem.serverHost = tiddler.fields['server.host'];
    if (syncItem.serverType && syncItem.serverHost) {
      syncItem.serverWorkspace = tiddler.fields['server.workspace'];
      syncItem.tiddler = tiddler;
      syncItem.title = tiddler.title;
      syncItem.isTouched = tiddler.isTouched();
      syncItem.selected = syncItem.isTouched;
      syncItem.syncStatus = config.macros.sync.syncStatusList[syncItem.isTouched ? "changedLocally" : "none"];
      syncItem.status = syncItem.syncStatus.text;
      list.push(syncItem);
    }
  });
  list.sort(function(a, b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
  return list;
};

config.macros.sync.preProcessSyncableTiddlers = function(syncList)
{
  for (var i = 0; i < syncList.length; i++) {
    var si = syncList[i];
    si.serverUrl = si.syncTask.syncMachine.generateTiddlerInfo(si.tiddler).uri;
  }
};

config.macros.sync.processSyncableTiddlers = function(syncList)
{
  for (var i = 0; i < syncList.length; i++) {
    var si = syncList[i];
    si.rowElement.style.display = si.syncStatus.display;
    if (si.syncStatus.className)
      si.rowElement.className = si.syncStatus.className;
  }
};

config.macros.sync.createSyncTasks = function(syncList)
{
  var syncTasks = [];
  for (var i = 0; i < syncList.length; i++) {
    var si = syncList[i];
    var r = null;
    for (var j = 0; j < syncTasks.length; j++) {
      var cst = syncTasks[j];
      if (si.serverType == cst.serverType && si.serverHost == cst.serverHost && si.serverWorkspace == cst.serverWorkspace)
        r = cst;
    }
    if (r) {
      si.syncTask = r;
      r.syncItems.push(si);
    }
    else {
      si.syncTask = this.createSyncTask(si);
      syncTasks.push(si.syncTask);
    }
  }
  return syncTasks;
};

config.macros.sync.createSyncTask = function(syncItem)
{
  var st = {};
  st.serverType = syncItem.serverType;
  st.serverHost = syncItem.serverHost;
  st.serverWorkspace = syncItem.serverWorkspace;
  st.syncItems = [syncItem];
  st.syncMachine = new SyncMachine(st.serverType, {
    start: function() {
      return this.openHost(st.serverHost, "openWorkspace");
    },
    openWorkspace: function() {
      return this.openWorkspace(st.serverWorkspace, "getTiddlerList");
    },
    getTiddlerList: function() {
      return this.getTiddlerList("onGetTiddlerList");
    },
    onGetTiddlerList: function(context) {
      var tiddlers = context.tiddlers;
      for (var i = 0; i < st.syncItems.length; i++) {
        var si = st.syncItems[i];
        var f = tiddlers.findByField("title", si.title);
        if (f !== null) {
          if (tiddlers[f].fields['server.page.revision'] > si.tiddler.fields['server.page.revision']) {
            si.syncStatus = config.macros.sync.syncStatusList[si.isTouched ? 'changedBoth' : 'changedServer'];
          }
        }
        else {
          si.syncStatus = config.macros.sync.syncStatusList.notFound;
        }
        config.macros.sync.updateSyncStatus(si);
      }
    },
    getTiddler: function(title) {
      return this.getTiddler(title, "onGetTiddler");
    },
    onGetTiddler: function(context) {
      var tiddler = context.tiddler;
      var syncItem = st.syncItems.findByField("title", tiddler.title);
      if (syncItem !== null) {
        syncItem = st.syncItems[syncItem];
        store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
        syncItem.syncStatus = config.macros.sync.syncStatusList.gotFromServer;
        config.macros.sync.updateSyncStatus(syncItem);
      }
    },
    putTiddler: function(tiddler) {
      return this.putTiddler(tiddler, "onPutTiddler");
    },
    onPutTiddler: function(context) {
      var title = context.title;
      var syncItem = st.syncItems.findByField("title", title);
      if (syncItem !== null) {
        syncItem = st.syncItems[syncItem];
        store.resetTiddler(title);
        if (context.status) {
          syncItem.syncStatus = config.macros.sync.syncStatusList.putToServer;
          config.macros.sync.updateSyncStatus(syncItem);
        }
      }
    }
  });
  st.syncMachine.go();
  return st;
};

config.macros.sync.updateSyncStatus = function(syncItem)
{
  var e = syncItem.colElements["status"];
  removeChildren(e);
  createTiddlyText(e, syncItem.syncStatus.text);
  syncItem.rowElement.style.display = syncItem.syncStatus.display;
  if (syncItem.syncStatus.className)
    syncItem.rowElement.className = syncItem.syncStatus.className;
};

config.macros.sync.doSync = function(e)
{
  var rowNames = ListView.getSelectedRows(currSync.listView);
  var sl = config.macros.sync.syncStatusList;
  for (var i = 0; i < currSync.syncList.length; i++) {
    var si = currSync.syncList[i];
    if (rowNames.indexOf(si.title) != -1) {
      var r = true;
      switch (si.syncStatus) {
        case sl.changedServer:
          r = si.syncTask.syncMachine.go("getTiddler", si.title);
          break;
        case sl.notFound:
        case sl.changedLocally:
        case sl.changedBoth:
          r = si.syncTask.syncMachine.go("putTiddler", si.tiddler);
          break;
        default:
          break;
      }
      if (!r)
        displayMessage("Error in doSync: " + r);
    }
  }
  return false;
};

function SyncMachine(serverType, steps)
{
  this.serverType = serverType;
  this.adaptor = new config.adaptors[serverType]();
  this.steps = steps;
}

SyncMachine.prototype.go = function(step, context)
{
  var r = context ? context.status : null;
  if (typeof r == "string") {
    this.invokeError(r);
    return r;
  }
  var h = this.steps[step ? step : "start"];
  if (!h)
    return null;
  r = h.call(this, context);
  if (typeof r == "string")
    this.invokeError(r);
  return r;
};

SyncMachine.prototype.invokeError = function(message)
{
  if (this.steps.error)
    this.steps.error(message);
};

SyncMachine.prototype.openHost = function(host, nextStep)
{
  var me = this;
  return me.adaptor.openHost(host, null, null, function(context) {me.go(nextStep, context);});
};

SyncMachine.prototype.getWorkspaceList = function(nextStep)
{
  var me = this;
  return me.adaptor.getWorkspaceList(null, null, function(context) {me.go(nextStep, context);});
};

SyncMachine.prototype.openWorkspace = function(workspace, nextStep)
{
  var me = this;
  return me.adaptor.openWorkspace(workspace, null, null, function(context) {me.go(nextStep, context);});
};

SyncMachine.prototype.getTiddlerList = function(nextStep)
{
  var me = this;
  return me.adaptor.getTiddlerList(null, null, function(context) {me.go(nextStep, context);});
};

SyncMachine.prototype.generateTiddlerInfo = function(tiddler)
{
  return this.adaptor.generateTiddlerInfo(tiddler);
};

SyncMachine.prototype.getTiddler = function(title, nextStep)
{
  var me = this;
  return me.adaptor.getTiddler(title, null, null, function(context) {me.go(nextStep, context);});
};

SyncMachine.prototype.putTiddler = function(tiddler, nextStep)
{
  var me = this;
  return me.adaptor.putTiddler(tiddler, null, null, function(context) {me.go(nextStep, context);});
};

//--
//-- Manager UI for groups of tiddlers
//--

config.macros.plugins.handler = function(place, macroName, params, wikifier, paramString)
{
  var wizard = new Wizard();
  wizard.createWizard(place, this.wizardTitle);
  wizard.addStep(this.step1Title, this.step1Html);
  var markList = wizard.getElement("markList");
  var listWrapper = document.createElement("div");
  markList.parentNode.insertBefore(listWrapper, markList);
  listWrapper.setAttribute("refresh", "macro");
  listWrapper.setAttribute("macroName", "plugins");
  listWrapper.setAttribute("params", paramString);
  this.refresh(listWrapper, paramString);
};

config.macros.plugins.refresh = function(listWrapper, params)
{
  var wizard = new Wizard(listWrapper);
  var selectedRows = [];
  ListView.forEachSelector(listWrapper, function(e, rowName) {
    if (e.checked)
      selectedRows.push(e.getAttribute("rowName"));
  });
  removeChildren(listWrapper);
  params = params.parseParams("anon");
  var plugins = installedPlugins.slice(0);
  var t,tiddler,p;
  var configTiddlers = store.getTaggedTiddlers("systemConfig");
  for (t = 0; t < configTiddlers.length; t++) {
    tiddler = configTiddlers[t];
    if (plugins.findByField("title", tiddler.title) == null) {
      p = getPluginInfo(tiddler);
      p.executed = false;
      p.log.splice(0, 0, this.skippedText);
      plugins.push(p);
    }
  }
  for (t = 0; t < plugins.length; t++) {
    p = plugins[t];
    p.size = p.tiddler.text ? p.tiddler.text.length : 0;
    p.forced = p.tiddler.isTagged("systemConfigForce");
    p.disabled = p.tiddler.isTagged("systemConfigDisable");
    p.Selected = selectedRows.indexOf(plugins[t].title) != -1;
  }
  if (plugins.length == 0) {
    createTiddlyElement(listWrapper, "em", null, null, this.noPluginText);
    wizard.setButtons([]);
  }
  else {
    var listView = ListView.create(listWrapper, plugins, this.listViewTemplate, this.onSelectCommand);
    wizard.setValue("listView", listView);
    wizard.setButtons([
      {
        caption: config.macros.plugins.removeLabel,
        tooltip: config.macros.plugins.removePrompt,
        onClick: config.macros.plugins.doRemoveTag
      },
      {
        caption: config.macros.plugins.deleteLabel,
        tooltip: config.macros.plugins.deletePrompt,
        onClick: config.macros.plugins.doDelete
      }
    ]);
  }
};

config.macros.plugins.doRemoveTag = function(e)
{
  var wizard = new Wizard(this);
  var listView = wizard.getValue("listView");
  var rowNames = ListView.getSelectedRows(listView);
  if (rowNames.length == 0) {
    alert(config.messages.nothingSelected);
  }
  else {
    for (var t = 0; t < rowNames.length; t++)
      store.setTiddlerTag(rowNames[t], false, "systemConfig");
  }
};

config.macros.plugins.doDelete = function(e)
{
  var wizard = new Wizard(this);
  var listView = wizard.getValue("listView");
  var rowNames = ListView.getSelectedRows(listView);
  if (rowNames.length == 0) {
    alert(config.messages.nothingSelected);
  }
  else {
    if (confirm(config.macros.plugins.confirmDeleteText.format([rowNames.join(", ")]))) {
      for (var t = 0; t < rowNames.length; t++) {
        store.removeTiddler(rowNames[t]);
        story.closeTiddler(rowNames[t], true);
      }
    }
  }
};

//--
//-- Message area
//--

function getMessageDiv()
{
  var msgArea = document.getElementById("messageArea");
  if (!msgArea)
    return null;
  if (!msgArea.hasChildNodes())
    createTiddlyButton(createTiddlyElement(msgArea, "div", null, "messageToolbar"),
            config.messages.messageClose.text,
            config.messages.messageClose.tooltip,
            clearMessage);
  msgArea.style.display = "block";
  return createTiddlyElement(msgArea, "div");
}

function displayMessage(text, linkText)
{
  var e = getMessageDiv();
  if (!e) {
    alert(text);
    return;
  }
  if (linkText) {
    var link = createTiddlyElement(e, "a", null, null, text);
    link.href = linkText;
    link.target = "_blank";
  }
  else {
    e.appendChild(document.createTextNode(text));
  }
}

function clearMessage()
{
  var msgArea = document.getElementById("messageArea");
  if (msgArea) {
    removeChildren(msgArea);
    msgArea.style.display = "none";
  }
  return false;
}

//--
//-- Refresh mechanism
//--

config.notifyTiddlers = [
  {
    name: "StyleSheetLayout",
    notify: refreshStyles
  },
  {
    name: "StyleSheetColors",
    notify: refreshStyles
  },
  {
    name: "StyleSheet",
    notify: refreshStyles
  },
  {
    name: "StyleSheetPrint",
    notify: refreshStyles
  },
  {
    name: "PageTemplate",
    notify: refreshPageTemplate
  },
  {
    name: "SiteTitle",
    notify: refreshPageTitle
  },
  {
    name: "SiteSubtitle",
    notify: refreshPageTitle
  },
  {
    name: "ColorPalette",
    notify: refreshColorPalette
  },
  {
    name: null,
    notify: refreshDisplay
  }
];

config.refreshers = {
  link: function(e, changeList)
  {
    var title = e.getAttribute("tiddlyLink");
    refreshTiddlyLink(e, title);
    return true;
  },

  tiddler: function(e, changeList)
  {
    var title = e.getAttribute("tiddler");
    var template = e.getAttribute("template");
    if (changeList && changeList.indexOf(title) != -1 && !story.isDirty(title))
      story.refreshTiddler(title, template, true);
    else
      refreshElements(e, changeList);
    return true;
  },

  content: function(e, changeList)
  {
    var title = e.getAttribute("tiddler");
    var force = e.getAttribute("force");
    if (force != null || changeList == null || changeList.indexOf(title) != -1) {
      removeChildren(e);
      wikify(store.getTiddlerText(title, ""), e, null, store.fetchTiddler(title));
      return true;
    }
    else
      return false;
  },

  macro: function(e, changeList)
  {
    var macro = e.getAttribute("macroName");
    var params = e.getAttribute("params");
    if (macro)
      macro = config.macros[macro];
    if (macro && macro.refresh)
      macro.refresh(e, params);
    return true;
  }
};

config.refresherData = {
  styleSheet: "StyleSheet",
  defaultStyleSheet: "StyleSheet",
  pageTemplate: "PageTemplate",
  defaultPageTemplate: "PageTemplate",
  colorPalette: "ColorPalette",
  defaultColorPalette: "ColorPalette"
};

function refreshElements(root, changeList)
{
  var nodes = root.childNodes;
  for (var c = 0; c < nodes.length; c++) {
    var e = nodes[c], type = null;
    if (e.getAttribute && (e.tagName ? e.tagName != "IFRAME" : true))
      type = e.getAttribute("refresh");
    var refresher = config.refreshers[type];
    var refreshed = false;
    if (refresher != undefined)
      refreshed = refresher(e, changeList);
    if (e.hasChildNodes() && !refreshed)
      refreshElements(e, changeList);
  }
}

function applyHtmlMacros(root, tiddler)
{
  var e = root.firstChild;
  while (e) {
    var nextChild = e.nextSibling;
    if (e.getAttribute) {
      var macro = e.getAttribute("macro");
      if (macro) {
        e.removeAttribute("macro");
        var params = "";
        var p = macro.indexOf(" ");
        if (p != -1) {
          params = macro.substr(p + 1);
          macro = macro.substr(0, p);
        }
        invokeMacro(e, macro, params, null, tiddler);
      }
    }
    if (e.hasChildNodes())
      applyHtmlMacros(e, tiddler);
    e = nextChild;
  }
}

function refreshPageTemplate(title)
{
  var stash = createTiddlyElement(document.body, "div");
  stash.style.display = "none";
  var display = story.getContainer();
  var nodes,t;
  if (display) {
    nodes = display.childNodes;
    for (t = nodes.length - 1; t >= 0; t--)
      stash.appendChild(nodes[t]);
  }
  var wrapper = document.getElementById("contentWrapper");

  var isAvailable = function(title) {
    var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
    if (s != -1)
      title = title.substr(0, s);
    return store.tiddlerExists(title) || store.isShadowTiddler(title);
  };
  if (!title || !isAvailable(title))
    title = config.refresherData.pageTemplate;
  if (!isAvailable(title))
    title = config.refresherData.defaultPageTemplate; //# this one is always avaialable
  wrapper.innerHTML = store.getRecursiveTiddlerText(title, null, 10);
  applyHtmlMacros(wrapper);
  refreshElements(wrapper);
  display = story.getContainer();
  removeChildren(display);
  if (!display)
    display = createTiddlyElement(wrapper, "div", story.containerId());
  nodes = stash.childNodes;
  for (t = nodes.length - 1; t >= 0; t--)
    display.appendChild(nodes[t]);
  removeNode(stash);
}

function refreshDisplay(hint)
{
  if (typeof hint == "string")
    hint = [hint];
  var e = document.getElementById("contentWrapper");
  refreshElements(e, hint);
  if (backstage.isPanelVisible()) {
    e = document.getElementById("backstage");
    refreshElements(e, hint);
  }
}

function refreshPageTitle()
{
  document.title = getPageTitle();
}

function getPageTitle()
{
  var st = wikifyPlain("SiteTitle");
  var ss = wikifyPlain("SiteSubtitle");
  return st + ((st == "" || ss == "") ? "" : " - ") + ss;
}

function refreshStyles(title, doc)
{
  setStylesheet(title == null ? "" : store.getRecursiveTiddlerText(title, "", 10), title, doc || document);
}

function refreshColorPalette(title)
{
  if (!startingUp)
    refreshAll();
}

function refreshAll()
{
  refreshPageTemplate();
  refreshDisplay();
  refreshStyles("StyleSheetLayout");
  refreshStyles("StyleSheetColors");
  refreshStyles(config.refresherData.styleSheet);
  refreshStyles("StyleSheetPrint");
}

//--
//-- Options stuff
//--

config.optionHandlers = {
  'txt': {
    get: function(name) {return encodeCookie(config.options[name].toString());},
    set: function(name, value) {config.options[name] = decodeCookie(value);}
  },
  'chk': {
    get: function(name) {return config.options[name] ? "true" : "false";},
    set: function(name, value) {config.options[name] = value == "true";}
  }
};

function loadOptionsCookie()
{
  if (safeMode)
    return;
  var cookies = document.cookie.split(";");
  for (var c = 0; c < cookies.length; c++) {
    var p = cookies[c].indexOf("=");
    if (p != -1) {
      var name = cookies[c].substr(0, p).trim();
      var value = cookies[c].substr(p + 1).trim();
      var optType = name.substr(0, 3);
      if (config.optionHandlers[optType] && config.optionHandlers[optType].set)
        config.optionHandlers[optType].set(name, value);
    }
  }
}

function saveOptionCookie(name)
{
  if (safeMode)
    return;
  var c = name + "=";
  var optType = name.substr(0, 3);
  if (config.optionHandlers[optType] && config.optionHandlers[optType].get)
    c += config.optionHandlers[optType].get(name);
  c += "; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/";
  document.cookie = c;
}

function encodeCookie(s)
{
  return escape(convertUnicodeToHtmlEntities(s));
}

function decodeCookie(s)
{
  s = unescape(s);
  var re = /&#[0-9]{1,5};/g;
  return s.replace(re, function($0) {return String.fromCharCode(eval($0.replace(/[&#;]/g, "")));});
}


config.macros.option.genericCreate = function(place, type, opt, className, desc)
{
  var typeInfo = config.macros.option.types[type];
  var c = document.createElement(typeInfo.elementType);
  if (typeInfo.typeValue)
    c.setAttribute("type", typeInfo.typeValue);
  c[typeInfo.eventName] = typeInfo.onChange;
  c.setAttribute("option", opt);
  c.className = className || typeInfo.className;
  if (config.optionsDesc[opt])
    c.setAttribute("title", config.optionsDesc[opt]);
  place.appendChild(c);
  if (desc != "no")
    createTiddlyText(place, config.optionsDesc[opt] || opt);
  c[typeInfo.valueField] = config.options[opt];
  return c;
};

config.macros.option.genericOnChange = function(e)
{
  var opt = this.getAttribute("option");
  if (opt) {
    var optType = opt.substr(0, 3);
    var handler = config.macros.option.types[optType];
    if (handler.elementType && handler.valueField)
      config.macros.option.propagateOption(opt, handler.valueField, this[handler.valueField], handler.elementType, this);
  }
  return true;
};

config.macros.option.types = {
  'txt': {
    elementType: "input",
    valueField: "value",
    eventName: "onchange",
    className: "txtOptionInput",
    create: config.macros.option.genericCreate,
    onChange: config.macros.option.genericOnChange
  },
  'chk': {
    elementType: "input",
    valueField: "checked",
    eventName: "onclick",
    className: "chkOptionInput",
    typeValue: "checkbox",
    create: config.macros.option.genericCreate,
    onChange: config.macros.option.genericOnChange
  }
};

config.macros.option.propagateOption = function(opt, valueField, value, elementType, elem)
{
  config.options[opt] = value;
  saveOptionCookie(opt);
  var nodes = document.getElementsByTagName(elementType);
  for (var t = 0; t < nodes.length; t++) {
    var optNode = nodes[t].getAttribute("option");
    if (opt == optNode && nodes[t] != elem)
      nodes[t][valueField] = value;
  }
};

config.macros.option.handler = function(place, macroName, params, wikifier, paramString)
{
  params = paramString.parseParams("anon", null, true, false, false);
  var opt = (params[1] && params[1].name == "anon") ? params[1].value : getParam(params, "name", null);
  var className = (params[2] && params[2].name == "anon") ? params[2].value : getParam(params, "class", null);
  var desc = getParam(params, "desc", "no");
  var type = opt.substr(0, 3);
  var h = config.macros.option.types[type];
  if (h && h.create)
    h.create(place, type, opt, className, desc);
};

config.macros.options.handler = function(place, macroName, params, wikifier, paramString)
{
  params = paramString.parseParams("anon", null, true, false, false);
  var showUnknown = getParam(params, "showUnknown", "no");
  var wizard = new Wizard();
  wizard.createWizard(place, this.wizardTitle);
  wizard.addStep(this.step1Title, this.step1Html);
  var markList = wizard.getElement("markList");
  var chkUnknown = wizard.getElement("chkUnknown");
  chkUnknown.checked = showUnknown == "yes";
  chkUnknown.onchange = this.onChangeUnknown;
  var listWrapper = document.createElement("div");
  markList.parentNode.insertBefore(listWrapper, markList);
  wizard.setValue("listWrapper", listWrapper);
  this.refreshOptions(listWrapper, showUnknown == "yes");
};

config.macros.options.refreshOptions = function(listWrapper, showUnknown)
{
  var opts = [];
  for (var n in config.options) {
    var opt = {};
    opt.option = "";
    opt.name = n;
    opt.lowlight = !config.optionsDesc[n];
    opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
    if (!opt.lowlight || showUnknown)
      opts.push(opt);
  }
  opts.sort(function(a, b) {return a.name.substr(3) < b.name.substr(3) ? -1 : (a.name.substr(3) == b.name.substr(3) ? 0 : +1);});
  var listview = ListView.create(listWrapper, opts, this.listViewTemplate);
  for (n = 0; n < opts.length; n++) {
    var type = opts[n].name.substr(0, 3);
    var h = config.macros.option.types[type];
    if (h && h.create) {
      h.create(opts[n].colElements['option'], type, opts[n].name, null, "no");
    }
  }
};

config.macros.options.onChangeUnknown = function(e)
{
  var wizard = new Wizard(this);
  var listWrapper = wizard.getValue("listWrapper");
  removeChildren(listWrapper);
  config.macros.options.refreshOptions(listWrapper, this.checked);
  return false;
};

//--
//-- Saving
//--

var saveUsingSafari = false;

var startSaveArea = '<div id="' + 'storeArea">'; // Split up into two so that indexOf() of this source doesn't find it
var endSaveArea = '</d' + 'iv>';

// If there are unsaved changes, force the user to confirm before exitting
function confirmExit()
{
  hadConfirmExit = true;
  if ((store && store.isDirty && store.isDirty()) || (story && story.areAnyDirty && story.areAnyDirty()))
    return config.messages.confirmExit;
}

// Give the user a chance to save changes before exitting
function checkUnsavedChanges()
{
  if (store && store.isDirty && store.isDirty() && window.hadConfirmExit === false) {
    if (confirm(config.messages.unsavedChangesWarning))
      saveChanges();
  }
}

function updateLanguageAttribute(s)
{
  if (config.locale) {
    var mRE = /(<html(?:.*?)?)(?: xml:lang\="([a-z]+)")?(?: lang\="([a-z]+)")?>/;
    var m = mRE.exec(s);
    if (m) {
      var t = m[1];
      if (m[2])
        t += ' xml:lang="' + config.locale + '"';
      if (m[3])
        t += ' lang="' + config.locale + '"';
      t += ">";
      s = s.substr(0, m.index) + t + s.substr(m.index + m[0].length);
    }
  }
  return s;
}

function updateMarkupBlock(s, blockName, tiddlerName)
{
  return s.replaceChunk(
          "<!--%0-START-->".format([blockName]),
          "<!--%0-END-->".format([blockName]),
          "\n" + convertUnicodeToFileFormat(store.getRecursiveTiddlerText(tiddlerName, "")) + "\n");
}

function updateOriginal(original, posDiv, localPath)
{
  if (!posDiv)
    posDiv = locateStoreArea(original);
  if (!posDiv) {
    alert(config.messages.invalidFileError.format([localPath]));
    return null;
  }
  var revised = original.substr(0, posDiv[0] + startSaveArea.length) + "\n" +
                convertUnicodeToFileFormat(store.allTiddlersAsHtml()) + "\n" +
                original.substr(posDiv[1]);
  var newSiteTitle = convertUnicodeToFileFormat(getPageTitle()).htmlEncode();
  revised = revised.replaceChunk("<title" + ">", "</title" + ">", " " + newSiteTitle + " ");
  revised = updateLanguageAttribute(revised);
  revised = updateMarkupBlock(revised, "PRE-HEAD", "MarkupPreHead");
  revised = updateMarkupBlock(revised, "POST-HEAD", "MarkupPostHead");
  revised = updateMarkupBlock(revised, "PRE-BODY", "MarkupPreBody");
  revised = updateMarkupBlock(revised, "POST-SCRIPT", "MarkupPostBody");
  return revised;
}

function locateStoreArea(original)
{
  // Locate the storeArea div's
  var posOpeningDiv = original.indexOf(startSaveArea);
  var limitClosingDiv = original.indexOf("<" + "!--POST-STOREAREA--" + ">");
  if (limitClosingDiv == -1)
    limitClosingDiv = original.indexOf("<" + "!--POST-BODY-START--" + ">");
  var posClosingDiv = original.lastIndexOf(endSaveArea, limitClosingDiv == -1 ? original.length : limitClosingDiv);
  return (posOpeningDiv != -1 && posClosingDiv != -1) ? [posOpeningDiv,posClosingDiv] : null;
}

function autoSaveChanges(onlyIfDirty, tiddlers)
{
  if (config.options.chkAutoSave)
    saveChanges(onlyIfDirty, tiddlers);
}

function loadOriginal(localPath)
{
  return loadFile(localPath);
}

// Save this tiddlywiki with the pending changes
function saveChanges(onlyIfDirty, tiddlers)
{
  if (onlyIfDirty && !store.isDirty())
    return;
  clearMessage();
  var t0 = new Date();
  var originalPath = document.location.toString();
  if (originalPath.substr(0, 5) != "file:") {
    alert(config.messages.notFileUrlError);
    if (store.tiddlerExists(config.messages.saveInstructions))
      story.displayTiddler(null, config.messages.saveInstructions);
    return;
  }
  var localPath = getLocalPath(originalPath);
  var original = loadOriginal(localPath);
  if (original == null) {
    alert(config.messages.cantSaveError);
    if (store.tiddlerExists(config.messages.saveInstructions))
      story.displayTiddler(null, config.messages.saveInstructions);
    return;
  }
  var posDiv = locateStoreArea(original);
  if (!posDiv) {
    alert(config.messages.invalidFileError.format([localPath]));
    return;
  }
  saveMain(localPath, original, posDiv);
  if (config.options.chkSaveBackups)
    saveBackup(localPath, original);
  if (config.options.chkSaveEmptyTemplate)
    saveEmpty(localPath, original, posDiv);
  if (config.options.chkGenerateAnRssFeed && saveRss instanceof Function)
    saveRss(localPath);
  if (config.options.chkDisplayInstrumentation)
    displayMessage("saveChanges " + (new Date() - t0) + " ms");
}

function saveMain(localPath, original, posDiv)
{
  var save;
  try {
    var revised = updateOriginal(original, posDiv, localPath);
    save = saveFile(localPath, revised);
  }
  catch (ex) {
    showException(ex);
  }
  if (save) {
    displayMessage(config.messages.mainSaved, "file://" + localPath);
    store.setDirty(false);
  }
  else {
    alert(config.messages.mainFailed);
  }
}

function saveBackup(localPath, original)
{
  var backupPath = getBackupPath(localPath);
  var backup = copyFile(backupPath, localPath);
  if (!backup)
    backup = saveFile(backupPath, original);
  if (backup)
    displayMessage(config.messages.backupSaved, "file://" + backupPath);
  else
    alert(config.messages.backupFailed);
}

function saveEmpty(localPath, original, posDiv)
{
  var emptyPath,p;
  if ((p = localPath.lastIndexOf("/")) != -1)
    emptyPath = localPath.substr(0, p) + "/";
  else if ((p = localPath.lastIndexOf("\\")) != -1)
    emptyPath = localPath.substr(0, p) + "\\";
  else
    emptyPath = localPath + ".";
  emptyPath += "empty.html";
  var empty = original.substr(0, posDiv[0] + startSaveArea.length) + original.substr(posDiv[1]);
  var emptySave = saveFile(emptyPath, empty);
  if (emptySave)
    displayMessage(config.messages.emptySaved, "file://" + emptyPath);
  else
    alert(config.messages.emptyFailed);
}

function getLocalPath(origPath)
{
  var originalPath = convertUriToUTF8(origPath, config.options.txtFileSystemCharSet);
  // Remove any location or query part of the URL
  var argPos = originalPath.indexOf("?");
  if (argPos != -1)
    originalPath = originalPath.substr(0, argPos);
  var hashPos = originalPath.indexOf("#");
  if (hashPos != -1)
    originalPath = originalPath.substr(0, hashPos);
  // Convert file://localhost/ to file:///
  if (originalPath.indexOf("file://localhost/") == 0)
    originalPath = "file://" + originalPath.substr(16);
  // Convert to a native file format
  var localPath;
  if (originalPath.charAt(9) == ":") // pc local file
    localPath = unescape(originalPath.substr(8)).replace(new RegExp("/", "g"), "\\");
  else if (originalPath.indexOf("file://///") == 0) // FireFox pc network file
    localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/", "g"), "\\");
  else if (originalPath.indexOf("file:///") == 0) // mac/unix local file
      localPath = unescape(originalPath.substr(7));
    else if (originalPath.indexOf("file:/") == 0) // mac/unix local file
        localPath = unescape(originalPath.substr(5));
      else // pc network file
        localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/", "g"), "\\");
  return localPath;
}

function getBackupPath(localPath, title, extension)
{
  var slash = "\\";
  var dirPathPos = localPath.lastIndexOf("\\");
  if (dirPathPos == -1) {
    dirPathPos = localPath.lastIndexOf("/");
    slash = "/";
  }
  var backupFolder = config.options.txtBackupFolder;
  if (!backupFolder || backupFolder == "")
    backupFolder = ".";
  var backupPath = localPath.substr(0, dirPathPos) + slash + backupFolder + localPath.substr(dirPathPos);
  backupPath = backupPath.substr(0, backupPath.lastIndexOf(".")) + ".";
  if (title)
    backupPath += title.replace(/[\\\/\*\?\":<> ]/g, "_") + ".";
  backupPath += (new Date()).convertToYYYYMMDDHHMMSSMMM() + "." + (extension || "html");
  return backupPath;
}

//--
//-- RSS Saving
//--

function saveRss(localPath)
{
  var rssPath = localPath.substr(0, localPath.lastIndexOf(".")) + ".xml";
  if (saveFile(rssPath, convertUnicodeToFileFormat(generateRss())))
    displayMessage(config.messages.rssSaved, "file://" + rssPath);
  else
    alert(config.messages.rssFailed);
}

function generateRss()
{
  var s = [];
  var d = new Date();
  var u = store.getTiddlerText("SiteUrl");
  // Assemble the header
  s.push("<" + "?xml version=\"1.0\"?" + ">");
  s.push("<rss version=\"2.0\">");
  s.push("<channel>");
  s.push("<title" + ">" + wikifyPlain("SiteTitle").htmlEncode() + "</title" + ">");
  if (u)
    s.push("<link>" + u.htmlEncode() + "</link>");
  s.push("<description>" + wikifyPlain("SiteSubtitle").htmlEncode() + "</description>");
  s.push("<language>" + config.locale + "</language>");
  s.push("<copyright>Copyright " + d.getFullYear() + " " + config.options.txtUserName.htmlEncode() + "</copyright>");
  s.push("<pubDate>" + d.toGMTString() + "</pubDate>");
  s.push("<lastBuildDate>" + d.toGMTString() + "</lastBuildDate>");
  s.push("<docs>http://blogs.law.harvard.edu/tech/rss</docs>");
  s.push("<generator>TiddlyWiki " + formatVersion() + "</generator>");
  // The body
  var tiddlers = store.getTiddlers("modified", "excludeLists");
  var n = config.numRssItems > tiddlers.length ? 0 : tiddlers.length - config.numRssItems;
  for (var t = tiddlers.length - 1; t >= n; t--) {
    s.push("<item>\n" + tiddlers[t].toRssItem(u) + "\n</item>");
  }
  // And footer
  s.push("</channel>");
  s.push("</rss>");
  // Save it all
  return s.join("\n");
}

//--
//-- Filesystem code
//--

function convertUTF8ToUnicode(u)
{
  return config.browser.isOpera || !window.netscape ? manualConvertUTF8ToUnicode(u) : mozConvertUTF8ToUnicode(u);
}

function manualConvertUTF8ToUnicode(utf)
{
  var uni = utf;
  var src = 0;
  var dst = 0;
  var b1, b2, b3;
  var c;
  while (src < utf.length) {
    b1 = utf.charCodeAt(src++);
    if (b1 < 0x80) {
      dst++;
    }
    else if (b1 < 0xE0) {
      b2 = utf.charCodeAt(src++);
      c = String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
      uni = uni.substring(0, dst++).concat(c, utf.substr(src));
    }
    else {
      b2 = utf.charCodeAt(src++);
      b3 = utf.charCodeAt(src++);
      c = String.fromCharCode(((b1 & 0xF) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
      uni = uni.substring(0, dst++).concat(c, utf.substr(src));
    }
  }
  return uni;
}

function mozConvertUTF8ToUnicode(u)
{
  try {
    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
    var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
    converter.charset = "UTF-8";
  }
  catch(ex) {
    return manualConvertUTF8ToUnicode(u);
  } // fallback
  var s = converter.ConvertToUnicode(u);
  var fin = converter.Finish();
  return fin.length > 0 ? s + fin : s;
}

function convertUnicodeToFileFormat(s)
{
  return config.browser.isOpera || !window.netscape ? convertUnicodeToHtmlEntities(s) : mozConvertUnicodeToUTF8(s);
}

function convertUnicodeToHtmlEntities(s)
{
  var re = /[^\u0000-\u007F]/g;
  return s.replace(re, function($0) {return "&#" + $0.charCodeAt(0).toString() + ";";});
}

function convertUnicodeToUTF8(s)
{
  // return convertUnicodeToFileFormat to allow plugin migration
  return convertUnicodeToFileFormat(s);
}

function manualConvertUnicodeToUTF8(s)
{
  return unescape(encodeURIComponent(s));
}

function mozConvertUnicodeToUTF8(s)
{
  try {
    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
    var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
    converter.charset = "UTF-8";
  }
  catch(ex) {
    return manualConvertUnicodeToUTF8(s);
  } // fallback
  var u = converter.ConvertFromUnicode(s);
  var fin = converter.Finish();
  return fin.length > 0 ? u + fin : u;
}

function convertUriToUTF8(uri, charSet)
{
  if (window.netscape == undefined || charSet == undefined || charSet == "")
    return uri;
  try {
    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
    var converter = Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService(Components.interfaces.nsIUTF8ConverterService);
  }
  catch(ex) {
    return uri;
  }
  return converter.convertURISpecToUTF8(uri, charSet);
}

function copyFile(dest, source)
{
  return config.browser.isIE ? ieCopyFile(dest, source) : false;
}

function saveFile(fileUrl, content)
{
  var r = mozillaSaveFile(fileUrl, content);
  if (!r)
    r = ieSaveFile(fileUrl, content);
  if (!r)
    r = javaSaveFile(fileUrl, content);
  return r;
}

function loadFile(fileUrl)
{
  var r = mozillaLoadFile(fileUrl);
  if ((r == null) || (r == false))
    r = ieLoadFile(fileUrl);
  if ((r == null) || (r == false))
    r = javaLoadFile(fileUrl);
  return r;
}

function ieCreatePath(path)
{
  try {
    var fso = new ActiveXObject("Scripting.FileSystemObject");
  }
  catch(ex) {
    return null;
  }

  var pos = path.lastIndexOf("\\");
  if (pos != -1)
    path = path.substring(0, pos + 1);

  var scan = [path];
  var parent = fso.GetParentFolderName(path);
  while (parent && !fso.FolderExists(parent)) {
    scan.push(parent);
    parent = fso.GetParentFolderName(parent);
  }

  for (i = scan.length - 1; i >= 0; i--) {
    if (!fso.FolderExists(scan[i])) {
      fso.CreateFolder(scan[i]);
    }
  }
  return true;
}

// Returns null if it can't do it, false if there's an error, true if it saved OK
function ieSaveFile(filePath, content)
{
  ieCreatePath(filePath);
  try {
    var fso = new ActiveXObject("Scripting.FileSystemObject");
  }
  catch(ex) {
    return null;
  }
  var file = fso.OpenTextFile(filePath, 2, -1, 0);
  file.Write(content);
  file.Close();
  return true;
}

// Returns null if it can't do it, false if there's an error, or a string of the content if successful
function ieLoadFile(filePath)
{
  try {
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    var file = fso.OpenTextFile(filePath, 1);
    var content = file.ReadAll();
    file.Close();
  }
  catch(ex) {
    return null;
  }
  return content;
}

function ieCopyFile(dest, source)
{
  ieCreatePath(dest);
  try {
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    fso.GetFile(source).Copy(dest);
  }
  catch(ex) {
    return false;
  }
  return true;
}

// Returns null if it can't do it, false if there's an error, true if it saved OK
function mozillaSaveFile(filePath, content)
{
  if (window.Components) {
    try {
      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
      var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
      file.initWithPath(filePath);
      if (!file.exists())
        file.create(0, 0664);
      var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
      out.init(file, 0x20 | 0x02, 00004, null);
      out.write(content, content.length);
      out.flush();
      out.close();
      return true;
    }
    catch(ex) {
      return false;
    }
  }
  return null;
}

// Returns null if it can't do it, false if there's an error, or a string of the content if successful
function mozillaLoadFile(filePath)
{
  if (window.Components) {
    try {
      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
      var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
      file.initWithPath(filePath);
      if (!file.exists())
        return null;
      var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
      inputStream.init(file, 0x01, 00004, null);
      var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
      sInputStream.init(inputStream);
      var contents = sInputStream.read(sInputStream.available());
      sInputStream.close();
      inputStream.close();
      return contents;
    }
    catch(ex) {
      return false;
    }
  }
  return null;
}

function javaUrlToFilename(url)
{
  var f = "//localhost";
  if (url.indexOf(f) == 0)
    return url.substring(f.length);
  var i = url.indexOf(":");
  return i > 0 ? url.substring(i - 1) : url;
}

function javaSaveFile(filePath, content)
{
  try {
    if (document.applets["TiddlySaver"])
      return document.applets["TiddlySaver"].saveFile(javaUrlToFilename(filePath), "UTF-8", content);
  }
  catch(ex) {
  }
  try {
    var s = new java.io.PrintStream(new java.io.FileOutputStream(javaUrlToFilename(filePath)));
    s.print(content);
    s.close();
  }
  catch(ex) {
    return null;
  }
  return true;
}

function javaLoadFile(filePath)
{
  try {
    if (document.applets["TiddlySaver"])
      return String(document.applets["TiddlySaver"].loadFile(javaUrlToFilename(filePath), "UTF-8"));
  }
  catch(ex) {
  }
  var content = [];
  try {
    var r = new java.io.BufferedReader(new java.io.FileReader(javaUrlToFilename(filePath)));
    var line;
    while ((line = r.readLine()) != null)
      content.push(new String(line));
    r.close();
  }
  catch(ex) {
    return null;
  }
  return content.join("\n");
}

//--
//-- Server adaptor base class
//--

function AdaptorBase()
{
  this.host = null;
  this.store = null;
  return this;
}

AdaptorBase.prototype.close = function()
{
  return true;
};

AdaptorBase.prototype.fullHostName = function(host)
{
  if (!host)
    return '';
  host = host.trim();
  if (!host.match(/:\/\//))
    host = 'http://' + host;
  if (host.substr(host.length - 1) == '/')
    host = host.substr(0, host.length - 1)
  return host;
};

AdaptorBase.minHostName = function(host)
{
  return host ? host.replace(/^http:\/\//, '').replace(/\/$/, '') : '';
};

AdaptorBase.prototype.setContext = function(context, userParams, callback)
{
  if (!context) context = {};
  context.userParams = userParams;
  if (callback) context.callback = callback;
  context.adaptor = this;
  if (!context.host)
    context.host = this.host;
  context.host = this.fullHostName(context.host);
  if (!context.workspace)
    context.workspace = this.workspace;
  return context;
};

// Open the specified host
AdaptorBase.prototype.openHost = function(host, context, userParams, callback)
{
  this.host = host;
  context = this.setContext(context, userParams, callback);
  context.status = true;
  if (callback)
    window.setTimeout(function() {context.callback(context, userParams);}, 10);
  return true;
};

// Open the specified workspace
AdaptorBase.prototype.openWorkspace = function(workspace, context, userParams, callback)
{
  this.workspace = workspace;
  context = this.setContext(context, userParams, callback);
  context.status = true;
  if (callback)
    window.setTimeout(function() {callback(context, userParams);}, 10);
  return true;
};

//--
//-- Server adaptor for talking to static TiddlyWiki files
//--

function FileAdaptor()
{
}

FileAdaptor.prototype = new AdaptorBase();

FileAdaptor.serverType = 'file';
FileAdaptor.serverLabel = 'TiddlyWiki';

FileAdaptor.loadTiddlyWikiCallback = function(status, context, responseText, url, xhr)
{
  context.status = status;
  if (!status) {
    context.statusText = "Error reading file";
  }
  else {
    context.adaptor.store = new TiddlyWiki();
    if (!context.adaptor.store.importTiddlyWiki(responseText)) {
      context.statusText = config.messages.invalidFileError.format([url]);
      context.status = false;
    }
  }
  context.complete(context, context.userParams);
};

// Get the list of workspaces on a given server
FileAdaptor.prototype.getWorkspaceList = function(context, userParams, callback)
{
  context = this.setContext(context, userParams, callback);
  context.workspaces = [
    {
      title:"(default)"
    }
  ];
  context.status = true;
  if (callback)
    window.setTimeout(function() {callback(context, userParams);}, 10);
  return true;
};

// Gets the list of tiddlers within a given workspace
FileAdaptor.prototype.getTiddlerList = function(context, userParams, callback, filter)
{
  context = this.setContext(context, userParams, callback);
  if (!context.filter)
    context.filter = filter;
  context.complete = FileAdaptor.getTiddlerListComplete;
  if (this.store) {
    var ret = context.complete(context, context.userParams);
  }
  else {
    ret = loadRemoteFile(context.host, FileAdaptor.loadTiddlyWikiCallback, context);
    if (typeof ret != "string")
      ret = true;
  }
  return ret;
};

FileAdaptor.getTiddlerListComplete = function(context, userParams)
{
  if (context.status) {
    if (context.filter) {
      context.tiddlers = context.adaptor.store.filterTiddlers(context.filter);
    }
    else {
      context.tiddlers = [];
      context.adaptor.store.forEachTiddler(function(title, tiddler) {context.tiddlers.push(tiddler);});
    }
    for (var i = 0; i < context.tiddlers.length; i++) {
      context.tiddlers[i].fields['server.type'] = FileAdaptor.serverType;
      context.tiddlers[i].fields['server.host'] = AdaptorBase.minHostName(context.host);
      context.tiddlers[i].fields['server.page.revision'] = context.tiddlers[i].modified.convertToYYYYMMDDHHMM();
    }
    context.status = true;
  }
  if (context.callback) {
    window.setTimeout(function() {context.callback(context, userParams);}, 10);
  }
  return true;
};

FileAdaptor.prototype.generateTiddlerInfo = function(tiddler)
{
  var info = {};
  info.uri = tiddler.fields['server.host'] + "#" + tiddler.title;
  return info;
};

// Retrieve a tiddler from a given workspace on a given server
FileAdaptor.prototype.getTiddler = function(title, context, userParams, callback)
{
  context = this.setContext(context, userParams, callback);
  context.title = title;
  context.complete = FileAdaptor.getTiddlerComplete;
  return context.adaptor.store ?
         context.complete(context, context.userParams) :
         loadRemoteFile(context.host, FileAdaptor.loadTiddlyWikiCallback, context);
};

FileAdaptor.getTiddlerComplete = function(context, userParams)
{
  var t = context.adaptor.store.fetchTiddler(context.title);
  t.fields['server.type'] = FileAdaptor.serverType;
  t.fields['server.host'] = AdaptorBase.minHostName(context.host);
  t.fields['server.page.revision'] = t.modified.convertToYYYYMMDDHHMM();
  context.tiddler = t;
  context.status = true;
  if (context.allowSynchronous) {
    context.isSynchronous = true;
    context.callback(context, userParams);
  }
  else {
    window.setTimeout(function() {context.callback(context, userParams);}, 10);
  }
  return true;
};

FileAdaptor.prototype.close = function()
{
  delete this.store;
  this.store = null;
};

config.adaptors[FileAdaptor.serverType] = FileAdaptor;

config.defaultAdaptor = FileAdaptor.serverType;

//--
//-- Remote HTTP requests
//--

function loadRemoteFile(url, callback, params)
{
  return httpReq("GET", url, callback, params);
}

function httpReq(type, url, callback, params, headers, data, contentType, username, password, allowCache)
{
  var x = null;
  try {
    x = new XMLHttpRequest(); //# Modern
  }
  catch(ex) {
    try {
      x = new ActiveXObject("Msxml2.XMLHTTP"); //# IE 6
    }
    catch(ex2) {
    }
  }
  if (!x)
    return "Can't create XMLHttpRequest object";
  x.onreadystatechange = function() {
    try {
      var status = x.status;
    }
    catch(ex) {
      status = false;
    }
    if (x.readyState == 4 && callback && (status !== undefined)) {
      if ([0, 200, 201, 204, 207].contains(status))
        callback(true, params, x.responseText, url, x);
      else
        callback(false, params, null, url, x);
      x.onreadystatechange = function() {};
      x = null;
    }
  };
  if (window.Components && window.netscape && window.netscape.security && document.location.protocol.indexOf("http") == -1)
    window.netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
  try {
    if (!allowCache)
      url = url + (url.indexOf("?") < 0 ? "?" : "&") + "nocache=" + Math.random();
    x.open(type, url, true, username, password);
    if (data)
      x.setRequestHeader("Content-Type", contentType || "application/x-www-form-urlencoded");
    if (x.overrideMimeType)
      x.setRequestHeader("Connection", "close");
    if (headers) {
      for (var n in headers)
        x.setRequestHeader(n, headers[n]);
    }
    x.setRequestHeader("X-Requested-With", "TiddlyWiki " + formatVersion());
    x.send(data);
  }
  catch(ex) {
    return exceptionText(ex);
  }
  return x;
}

// included for compatibility
function getXMLHttpRequest()
{
  try {
    var x = new XMLHttpRequest(); // Modern
  }
  catch(ex) {
    try {
      x = new ActiveXObject("Msxml2.XMLHTTP"); // IE 6
    }
    catch (ex2) {
      return null;
    }
  }
  return x;
}

// included for compatibility
function doHttp(type, url, data, contentType, username, password, callback, params, headers, allowCache)
{
  return httpReq(type, url, callback, params, headers, data, contentType, username, password, allowCache);
}

//--
//-- TiddlyWiki-specific utility functions
//--

function formatVersion(v)
{
  v = v || version;
  return v.major + "." + v.minor + "." + v.revision + (v.beta ? " (beta " + v.beta + ")" : "");
}

function compareVersions(v1, v2)
{
  var a = ["major","minor","revision"];
  for (var i = 0; i < a.length; i++) {
    var x1 = v1[a[i]] || 0;
    var x2 = v2[a[i]] || 0;
    if (x1 < x2)
      return 1;
    if (x1 > x2)
      return -1;
  }
  x1 = v1.beta || 9999;
  x2 = v2.beta || 9999;
  if (x1 < x2)
    return 1;
  return x1 > x2 ? -1 : 0;
}

function createTiddlyButton(parent, text, tooltip, action, className, id, accessKey, attribs)
{
  var btn = document.createElement("a");
  if (action) {
    btn.onclick = action;
    btn.setAttribute("href", "javascript:;");
  }
  if (tooltip)
    btn.setAttribute("title", tooltip);
  if (text)
    btn.appendChild(document.createTextNode(text));
  btn.className = className || "button";
  if (id)
    btn.id = id;
  if (attribs) {
    for (var i in attribs) {
      btn.setAttribute(i, attribs[i]);
    }
  }
  if (parent)
    parent.appendChild(btn);
  if (accessKey)
    btn.setAttribute("accessKey", accessKey);
  return btn;
}

function createTiddlyLink(place, title, includeText, className, isStatic, linkedFromTiddler, noToggle)
{
  var text = includeText ? title : null;
  var i = getTiddlyLinkInfo(title, className);
  var btn = isStatic ? createExternalLink(place, store.getTiddlerText("SiteUrl", null) + "#" + title) : createTiddlyButton(place, text, i.subTitle, onClickTiddlerLink, i.classes);
  if (isStatic)
    btn.className += ' ' + className;
  btn.setAttribute("refresh", "link");
  btn.setAttribute("tiddlyLink", title);
  if (noToggle)
    btn.setAttribute("noToggle", "true");
  if (linkedFromTiddler) {
    var fields = linkedFromTiddler.getInheritedFields();
    if (fields)
      btn.setAttribute("tiddlyFields", fields);
  }
  return btn;
}

function refreshTiddlyLink(e, title)
{
  var i = getTiddlyLinkInfo(title, e.className);
  e.className = i.classes;
  e.title = i.subTitle;
}

function getTiddlyLinkInfo(title, currClasses)
{
  var classes = currClasses ? currClasses.split(" ") : [];
  classes.pushUnique("tiddlyLink");
  var tiddler = store.fetchTiddler(title);
  var subTitle;
  if (tiddler) {
    subTitle = tiddler.getSubtitle();
    classes.pushUnique("tiddlyLinkExisting");
    classes.remove("tiddlyLinkNonExisting");
    classes.remove("shadow");
  }
  else {
    classes.remove("tiddlyLinkExisting");
    classes.pushUnique("tiddlyLinkNonExisting");
    if (store.isShadowTiddler(title)) {
      subTitle = config.messages.shadowedTiddlerToolTip.format([title]);
      classes.pushUnique("shadow");
    }
    else {
      subTitle = config.messages.undefinedTiddlerToolTip.format([title]);
      classes.remove("shadow");
    }
  }
  if (typeof config.annotations[title] == "string")
    subTitle = config.annotations[title];
  return {classes: classes.join(" "),subTitle: subTitle};
}

function createExternalLink(place, url)
{
  var link = document.createElement("a");
  link.className = "externalLink";
  link.href = url;
  link.title = config.messages.externalLinkTooltip.format([url]);
  if (config.options.chkOpenInNewWindow)
    link.target = "_blank";
  place.appendChild(link);
  return link;
}

// Event handler for clicking on a tiddly link
function onClickTiddlerLink(ev)
{
  var e = ev || window.event;
  var target = resolveTarget(e);
  var link = target;
  var title = null;
  var fields = null;
  var noToggle = null;
  do {
    title = link.getAttribute("tiddlyLink");
    fields = link.getAttribute("tiddlyFields");
    noToggle = link.getAttribute("noToggle");
    link = link.parentNode;
  }
  while (title == null && link != null);
  if (!store.isShadowTiddler(title)) {
    var f = fields ? fields.decodeHashMap() : {};
    fields = String.encodeHashMap(merge(f, config.defaultCustomFields, true));
  }
  if (title) {
    var toggling = e.metaKey || e.ctrlKey;
    if (config.options.chkToggleLinks)
      toggling = !toggling;
    if (noToggle)
      toggling = false;
    if (store.getTiddler(title))
      fields = null;
    story.displayTiddler(target, title, null, true, null, fields, toggling);
  }
  clearMessage();
  return false;
}

// Create a button for a tag with a popup listing all the tiddlers that it tags
function createTagButton(place, tag, excludeTiddler, title, tooltip)
{
  var btn = createTiddlyButton(place, title || tag, (tooltip || config.views.wikified.tag.tooltip).format([tag]), onClickTag);
  btn.setAttribute("tag", tag);
  if (excludeTiddler)
    btn.setAttribute("tiddler", excludeTiddler);
  return btn;
}

// Event handler for clicking on a tiddler tag
function onClickTag(ev)
{
  var e = ev || window.event;
  var popup = Popup.create(this);
  var tag = this.getAttribute("tag");
  var title = this.getAttribute("tiddler");
  if (popup && tag) {
    var tagged = store.getTaggedTiddlers(tag);
    var titles = [];
    var li,r;
    for (r = 0; r < tagged.length; r++) {
      if (tagged[r].title != title)
        titles.push(tagged[r].title);
    }
    var lingo = config.views.wikified.tag;
    if (titles.length > 0) {
      var openAll = createTiddlyButton(createTiddlyElement(popup, "li"), lingo.openAllText.format([tag]), lingo.openAllTooltip, onClickTagOpenAll);
      openAll.setAttribute("tag", tag);
      createTiddlyElement(createTiddlyElement(popup, "li", null, "listBreak"), "div");
      for (r = 0; r < titles.length; r++) {
        createTiddlyLink(createTiddlyElement(popup, "li"), titles[r], true);
      }
    }
    else {
      createTiddlyText(createTiddlyElement(popup, "li", null, "disabled"), lingo.popupNone.format([tag]));
    }
    createTiddlyElement(createTiddlyElement(popup, "li", null, "listBreak"), "div");
    var h = createTiddlyLink(createTiddlyElement(popup, "li"), tag, false);
    createTiddlyText(h, lingo.openTag.format([tag]));
  }
  Popup.show();
  e.cancelBubble = true;
  if (e.stopPropagation) e.stopPropagation();
  return false;
}

// Event handler for 'open all' on a tiddler popup
function onClickTagOpenAll(ev)
{
  var tiddlers = store.getTaggedTiddlers(this.getAttribute("tag"));
  story.displayTiddlers(this, tiddlers);
  return false;
}

function onClickError(ev)
{
  var e = ev || window.event;
  var popup = Popup.create(this);
  var lines = this.getAttribute("errorText").split("\n");
  for (var t = 0; t < lines.length; t++)
    createTiddlyElement(popup, "li", null, null, lines[t]);
  Popup.show();
  e.cancelBubble = true;
  if (e.stopPropagation) e.stopPropagation();
  return false;
}

function createTiddlyDropDown(place, onchange, options, defaultValue)
{
  var sel = createTiddlyElement(place, "select");
  sel.onchange = onchange;
  for (var t = 0; t < options.length; t++) {
    var e = createTiddlyElement(sel, "option", null, null, options[t].caption);
    e.value = options[t].name;
    if (options[t].name == defaultValue)
      e.selected = true;
  }
  return sel;
}

function createTiddlyPopup(place, caption, tooltip, tiddler)
{
  if (tiddler.text) {
    createTiddlyLink(place, caption, true);
    var btn = createTiddlyButton(place, glyph("downArrow"), tooltip, onClickTiddlyPopup, "tiddlerPopupButton");
    btn.tiddler = tiddler;
  }
  else {
    createTiddlyText(place, caption);
  }
}

function onClickTiddlyPopup(ev)
{
  var e = ev || window.event;
  var tiddler = this.tiddler;
  if (tiddler.text) {
    var popup = Popup.create(this, "div", "popupTiddler");
    wikify(tiddler.text, popup, null, tiddler);
    Popup.show();
  }
  if (e) e.cancelBubble = true;
  if (e && e.stopPropagation) e.stopPropagation();
  return false;
}

function createTiddlyError(place, title, text)
{
  var btn = createTiddlyButton(place, title, null, onClickError, "errorButton");
  if (text) btn.setAttribute("errorText", text);
}

function merge(dst, src, preserveExisting)
{
  for (var i in src) {
    if (!preserveExisting || dst[i] === undefined)
      dst[i] = src[i];
  }
  return dst;
}

// Returns a string containing the description of an exception, optionally prepended by a message
function exceptionText(e, message)
{
  var s = e.description || e.toString();
  return message ? "%0:\n%1".format([message,s]) : s;
}

// Displays an alert of an exception description with optional message
function showException(e, message)
{
  alert(exceptionText(e, message));
}

function alertAndThrow(m)
{
  alert(m);
  throw(m);
}

function glyph(name)
{
  var g = config.glyphs;
  var b = g.currBrowser;
  if (b == null) {
    b = 0;
    while (!g.browsers[b]() && b < g.browsers.length - 1)
      b++;
    g.currBrowser = b;
  }
  if (!g.codes[name])
    return "";
  return g.codes[name][b];
}

if (!window.console) {
  console = {log:function(message) {displayMessage(message);}};
}

//-
//- Animation engine
//-

function Animator()
{
  this.running = 0; // Incremented at start of each animation, decremented afterwards. If zero, the interval timer is disabled
  this.timerID = 0; // ID of the timer used for animating
  this.animations = []; // List of animations in progress
  return this;
}

// Start animation engine
Animator.prototype.startAnimating = function() //# Variable number of arguments
{
  for (var t = 0; t < arguments.length; t++)
    this.animations.push(arguments[t]);
  if (this.running == 0) {
    var me = this;
    this.timerID = window.setInterval(function() {me.doAnimate(me);}, 10);
  }
  this.running += arguments.length;
};

// Perform an animation engine tick, calling each of the known animation modules
Animator.prototype.doAnimate = function(me)
{
  var a = 0;
  while (a < me.animations.length) {
    var animation = me.animations[a];
    if (animation.tick()) {
      a++;
    }
    else {
      me.animations.splice(a, 1);
      if (--me.running == 0)
        window.clearInterval(me.timerID);
    }
  }
};

Animator.slowInSlowOut = function(progress)
{
  return(1 - ((Math.cos(progress * Math.PI) + 1) / 2));
};

//--
//-- Morpher animation
//--

// Animate a set of properties of an element
function Morpher(element, duration, properties, callback)
{
  this.element = element;
  this.duration = duration;
  this.properties = properties;
  this.startTime = new Date();
  this.endTime = Number(this.startTime) + duration;
  this.callback = callback;
  this.tick();
  return this;
}

Morpher.prototype.assignStyle = function(element, style, value)
{
  switch (style) {
    case "-tw-vertScroll":
      window.scrollTo(findScrollX(), value);
      break;
    case "-tw-horizScroll":
      window.scrollTo(value, findScrollY());
      break;
    default:
      element.style[style] = value;
      break;
  }
};

Morpher.prototype.stop = function()
{
  for (var t = 0; t < this.properties.length; t++) {
    var p = this.properties[t];
    if (p.atEnd !== undefined) {
      this.assignStyle(this.element, p.style, p.atEnd);
    }
  }
  if (this.callback)
    this.callback(this.element, this.properties);
};

Morpher.prototype.tick = function()
{
  var currTime = Number(new Date());
  var progress = Animator.slowInSlowOut(Math.min(1, (currTime - this.startTime) / this.duration));
  for (var t = 0; t < this.properties.length; t++) {
    var p = this.properties[t];
    if (p.start !== undefined && p.end !== undefined) {
      var template = p.template || "%0";
      switch (p.format) {
        case undefined:
        case "style":
          var v = p.start + (p.end - p.start) * progress;
          this.assignStyle(this.element, p.style, template.format([v]));
          break;
        case "color":
          break;
      }
    }
  }
  if (currTime >= this.endTime) {
    this.stop();
    return false;
  }
  return true;
};

//--
//-- Zoomer animation
//--

function Zoomer(text, startElement, targetElement, unused)
{
  var e = createTiddlyElement(document.body, "div", null, "zoomer");
  createTiddlyElement(e, "div", null, null, text);
  var winWidth = findWindowWidth();
  var winHeight = findWindowHeight();
  var p = [
    {
      style: 'left',
      start: findPosX(startElement),
      end: findPosX(targetElement),
      template: '%0px'
    },
    {
      style: 'top',
      start: findPosY(startElement),
      end: findPosY(targetElement),
      template: '%0px'
    },
    {
      style: 'width',
      start: Math.min(startElement.scrollWidth, winWidth),
      end: Math.min(targetElement.scrollWidth, winWidth),
      template: '%0px',
      atEnd: 'auto'
    },
    {
      style: 'height',
      start: Math.min(startElement.scrollHeight, winHeight),
      end: Math.min(targetElement.scrollHeight, winHeight),
      template: '%0px',
      atEnd: 'auto'
    },
    {
      style: 'fontSize',
      start: 8,
      end: 24,
      template: '%0pt'
    }
  ];
  var c = function(element, properties) {removeNode(element);};
  return new Morpher(e, config.animDuration, p, c);
}

//--
//-- Scroller animation
//--

function Scroller(targetElement)
{
  var p = [
    {
      style: '-tw-vertScroll',
      start: findScrollY(),
      end: ensureVisible(targetElement)
    }
  ];
  return new Morpher(targetElement, config.animDuration, p);
}

//--
//-- Slider animation
//--

// deleteMode - "none", "all" [delete target element and it's children], [only] "children" [but not the target element]
function Slider(element, opening, unused, deleteMode)
{
  element.style.overflow = 'hidden';
  if (opening)
    element.style.height = '0px'; // Resolves a Firefox flashing bug
  element.style.display = 'block';
  var left = findPosX(element);
  var width = element.scrollWidth;
  var height = element.scrollHeight;
  var winWidth = findWindowWidth();
  var p = [];
  var c = null;
  if (opening) {
    p.push({style: 'height', start: 0, end: height, template: '%0px', atEnd: 'auto'});
    p.push({style: 'opacity', start: 0, end: 1, template: '%0'});
    p.push({style: 'filter', start: 0, end: 100, template: 'alpha(opacity:%0)'});
  }
  else {
    p.push({style: 'height', start: height, end: 0, template: '%0px'});
    p.push({style: 'display', atEnd: 'none'});
    p.push({style: 'opacity', start: 1, end: 0, template: '%0'});
    p.push({style: 'filter', start: 100, end: 0, template: 'alpha(opacity:%0)'});
    switch (deleteMode) {
      case "all":
        c = function(element, properties) {removeNode(element);};
        break;
      case "children":
        c = function(element, properties) {removeChildren(element);};
        break;
    }
  }
  return new Morpher(element, config.animDuration, p, c);
}

//--
//-- Popup menu
//--

var Popup = {
  stack: [] // Array of objects with members root: and popup:
};

Popup.create = function(root, elem, className)
{
  var stackPosition = this.find(root, "popup");
  Popup.remove(stackPosition + 1);
  var popup = createTiddlyElement(document.body, elem || "ol", "popup", className || "popup");
  popup.stackPosition = stackPosition;
  Popup.stack.push({root: root, popup: popup});
  return popup;
};

Popup.onDocumentClick = function(ev)
{
  var e = ev || window.event;
  if (e.eventPhase == undefined)
    Popup.remove();
  else if (e.eventPhase == Event.BUBBLING_PHASE || e.eventPhase == Event.AT_TARGET)
    Popup.remove();
  return true;
};

Popup.show = function(valign, halign, offset)
{
  var curr = Popup.stack[Popup.stack.length - 1];
  this.place(curr.root, curr.popup, valign, halign, offset);
  addClass(curr.root, "highlight");
  if (config.options.chkAnimate && anim && typeof Scroller == "function")
    anim.startAnimating(new Scroller(curr.popup));
  else
    window.scrollTo(0, ensureVisible(curr.popup));
};

Popup.place = function(root, popup, valign, halign, offset)
{
  if (!offset)
    var offset = {x:0,y:0};
  if (popup.stackPosition >= 0 && !valign && !halign) {
    offset.x = offset.x + root.offsetWidth;
  }
  else {
    offset.x = (halign == 'right') ? offset.x + root.offsetWidth : offset.x;
    offset.y = (valign == 'top') ? offset.y : offset.y + root.offsetHeight;
  }
  var rootLeft = findPosX(root);
  var rootTop = findPosY(root);
  var popupLeft = rootLeft + offset.x;
  var popupTop = rootTop + offset.y;
  var winWidth = findWindowWidth();
  if (popup.offsetWidth > winWidth * 0.75)
    popup.style.width = winWidth * 0.75 + "px";
  var popupWidth = popup.offsetWidth;
  var scrollWidth = winWidth - document.body.offsetWidth;
  if (popupLeft + popupWidth > winWidth - scrollWidth - 1) {
    if (halign == 'right')
      popupLeft = popupLeft - root.offsetWidth - popupWidth;
    else
      popupLeft = winWidth - popupWidth - scrollWidth - 1;
  }
  popup.style.left = popupLeft + "px";
  popup.style.top = popupTop + "px";
  popup.style.display = "block";
};

Popup.find = function(e)
{
  var pos = -1;
  for (var t = this.stack.length - 1; t >= 0; t--) {
    if (isDescendant(e, this.stack[t].popup))
      pos = t;
  }
  return pos;
};

Popup.remove = function(pos)
{
  if (!pos) var pos = 0;
  if (Popup.stack.length > pos) {
    Popup.removeFrom(pos);
  }
};

Popup.removeFrom = function(from)
{
  for (var t = Popup.stack.length - 1; t >= from; t--) {
    var p = Popup.stack[t];
    removeClass(p.root, "highlight");
    removeNode(p.popup);
  }
  Popup.stack = Popup.stack.slice(0, from);
};

//--
//-- Wizard support
//--

function Wizard(elem)
{
  if (elem) {
    this.formElem = findRelated(elem, "wizard", "className");
    this.bodyElem = findRelated(this.formElem.firstChild, "wizardBody", "className", "nextSibling");
    this.footElem = findRelated(this.formElem.firstChild, "wizardFooter", "className", "nextSibling");
  }
  else {
    this.formElem = null;
    this.bodyElem = null;
    this.footElem = null;
  }
}

Wizard.prototype.setValue = function(name, value)
{
  if (this.formElem)
    this.formElem[name] = value;
};

Wizard.prototype.getValue = function(name)
{
  return this.formElem ? this.formElem[name] : null;
};

Wizard.prototype.createWizard = function(place, title)
{
  this.formElem = createTiddlyElement(place, "form", null, "wizard");
  createTiddlyElement(this.formElem, "h1", null, null, title);
  this.bodyElem = createTiddlyElement(this.formElem, "div", null, "wizardBody");
  this.footElem = createTiddlyElement(this.formElem, "div", null, "wizardFooter");
};

Wizard.prototype.clear = function()
{
  removeChildren(this.bodyElem);
};

Wizard.prototype.setButtons = function(buttonInfo, status)
{
  removeChildren(this.footElem);
  for (var t = 0; t < buttonInfo.length; t++) {
    createTiddlyButton(this.footElem, buttonInfo[t].caption, buttonInfo[t].tooltip, buttonInfo[t].onClick);
    insertSpacer(this.footElem);
  }
  if (typeof status == "string") {
    createTiddlyElement(this.footElem, "span", null, "status", status);
  }
};

Wizard.prototype.addStep = function(stepTitle, html)
{
  removeChildren(this.bodyElem);
  var w = createTiddlyElement(this.bodyElem, "div");
  createTiddlyElement(w, "h2", null, null, stepTitle);
  var step = createTiddlyElement(w, "div", null, "wizardStep");
  step.innerHTML = html;
  applyHtmlMacros(step, tiddler);
};

Wizard.prototype.getElement = function(name)
{
  return this.formElem.elements[name];
};

//--
//-- ListView gadget
//--

var ListView = {};

// Create a listview
ListView.create = function(place, listObject, listTemplate, callback, className)
{
  var table = createTiddlyElement(place, "table", null, className || "listView twtable");
  var thead = createTiddlyElement(table, "thead");
  var r = createTiddlyElement(thead, "tr");
  for (var t = 0; t < listTemplate.columns.length; t++) {
    var columnTemplate = listTemplate.columns[t];
    var c = createTiddlyElement(r, "th");
    var colType = ListView.columnTypes[columnTemplate.type];
    if (colType && colType.createHeader) {
      colType.createHeader(c, columnTemplate, t);
      if (columnTemplate.className)
        addClass(c, columnTemplate.className);
    }
  }
  var tbody = createTiddlyElement(table, "tbody");
  for (var rc = 0; rc < listObject.length; rc++) {
    var rowObject = listObject[rc];
    r = createTiddlyElement(tbody, "tr");
    for (c = 0; c < listTemplate.rowClasses.length; c++) {
      if (rowObject[listTemplate.rowClasses[c].field])
        addClass(r, listTemplate.rowClasses[c].className);
    }
    rowObject.rowElement = r;
    rowObject.colElements = {};
    for (var cc = 0; cc < listTemplate.columns.length; cc++) {
      c = createTiddlyElement(r, "td");
      columnTemplate = listTemplate.columns[cc];
      var field = columnTemplate.field;
      colType = ListView.columnTypes[columnTemplate.type];
      if (colType && colType.createItem) {
        colType.createItem(c, rowObject, field, columnTemplate, cc, rc);
        if (columnTemplate.className)
          addClass(c, columnTemplate.className);
      }
      rowObject.colElements[field] = c;
    }
  }
  if (callback && listTemplate.actions)
    createTiddlyDropDown(place, ListView.getCommandHandler(callback), listTemplate.actions);
  if (callback && listTemplate.buttons) {
    for (t = 0; t < listTemplate.buttons.length; t++) {
      var a = listTemplate.buttons[t];
      if (a && a.name != "")
        createTiddlyButton(place, a.caption, null, ListView.getCommandHandler(callback, a.name, a.allowEmptySelection));
    }
  }
  return table;
};

ListView.getCommandHandler = function(callback, name, allowEmptySelection)
{
  return function(e) {
    var view = findRelated(this, "TABLE", null, "previousSibling");
    var tiddlers = [];
    ListView.forEachSelector(view, function(e, rowName) {
      if (e.checked)
        tiddlers.push(rowName);
    });
    if (tiddlers.length == 0 && !allowEmptySelection) {
      alert(config.messages.nothingSelected);
    }
    else {
      if (this.nodeName.toLowerCase() == "select") {
        callback(view, this.value, tiddlers);
        this.selectedIndex = 0;
      }
      else {
        callback(view, name, tiddlers);
      }
    }
  };
};

// Invoke a callback for each selector checkbox in the listview
ListView.forEachSelector = function(view, callback)
{
  var checkboxes = view.getElementsByTagName("input");
  var hadOne = false;
  for (var t = 0; t < checkboxes.length; t++) {
    var cb = checkboxes[t];
    if (cb.getAttribute("type") == "checkbox") {
      var rn = cb.getAttribute("rowName");
      if (rn) {
        callback(cb, rn);
        hadOne = true;
      }
    }
  }
  return hadOne;
};

ListView.getSelectedRows = function(view)
{
  var rowNames = [];
  ListView.forEachSelector(view, function(e, rowName) {
    if (e.checked)
      rowNames.push(rowName);
  });
  return rowNames;
};

ListView.columnTypes = {};

ListView.columnTypes.String = {
  createHeader: function(place, columnTemplate, col)
  {
    createTiddlyText(place, columnTemplate.title);
  },
  createItem: function(place, listObject, field, columnTemplate, col, row)
  {
    var v = listObject[field];
    if (v != undefined)
      createTiddlyText(place, v);
  }
};

ListView.columnTypes.WikiText = {
  createHeader: ListView.columnTypes.String.createHeader,
  createItem: function(place, listObject, field, columnTemplate, col, row)
  {
    var v = listObject[field];
    if (v != undefined)
      wikify(v, place, null, null);
  }
};

ListView.columnTypes.Tiddler = {
  createHeader: ListView.columnTypes.String.createHeader,
  createItem: function(place, listObject, field, columnTemplate, col, row)
  {
    var v = listObject[field];
    if (v != undefined && v.title)
      createTiddlyPopup(place, v.title, config.messages.listView.tiddlerTooltip, v);
  }
};

ListView.columnTypes.Size = {
  createHeader: ListView.columnTypes.String.createHeader,
  createItem: function(place, listObject, field, columnTemplate, col, row)
  {
    var v = listObject[field];
    if (v != undefined) {
      var t = 0;
      while (t < config.messages.sizeTemplates.length - 1 && v < config.messages.sizeTemplates[t].unit)
        t++;
      createTiddlyText(place, config.messages.sizeTemplates[t].template.format([Math.round(v / config.messages.sizeTemplates[t].unit)]));
    }
  }
};

ListView.columnTypes.Link = {
  createHeader: ListView.columnTypes.String.createHeader,
  createItem: function(place, listObject, field, columnTemplate, col, row)
  {
    var v = listObject[field];
    var c = columnTemplate.text;
    if (v != undefined)
      createTiddlyText(createExternalLink(place, v), c || v);
  }
};

ListView.columnTypes.Date = {
  createHeader: ListView.columnTypes.String.createHeader,
  createItem: function(place, listObject, field, columnTemplate, col, row)
  {
    var v = listObject[field];
    if (v != undefined)
      createTiddlyText(place, v.formatString(columnTemplate.dateFormat));
  }
};

ListView.columnTypes.StringList = {
  createHeader: ListView.columnTypes.String.createHeader,
  createItem: function(place, listObject, field, columnTemplate, col, row)
  {
    var v = listObject[field];
    if (v != undefined) {
      for (var t = 0; t < v.length; t++) {
        createTiddlyText(place, v[t]);
        createTiddlyElement(place, "br");
      }
    }
  }
};

ListView.columnTypes.Selector = {
  createHeader: function(place, columnTemplate, col)
  {
    createTiddlyCheckbox(place, null, false, this.onHeaderChange);
  },
  createItem: function(place, listObject, field, columnTemplate, col, row)
  {
    var e = createTiddlyCheckbox(place, null, listObject[field], null);
    e.setAttribute("rowName", listObject[columnTemplate.rowName]);
  },
  onHeaderChange: function(e)
  {
    var state = this.checked;
    var view = findRelated(this, "TABLE");
    if (!view)
      return;
    ListView.forEachSelector(view, function(e, rowName) {
      e.checked = state;
    });
  }
};

ListView.columnTypes.Tags = {
  createHeader: ListView.columnTypes.String.createHeader,
  createItem: function(place, listObject, field, columnTemplate, col, row)
  {
    var tags = listObject[field];
    createTiddlyText(place, String.encodeTiddlyLinkList(tags));
  }
};

ListView.columnTypes.Boolean = {
  createHeader: ListView.columnTypes.String.createHeader,
  createItem: function(place, listObject, field, columnTemplate, col, row)
  {
    if (listObject[field] == true)
      createTiddlyText(place, columnTemplate.trueText);
    if (listObject[field] == false)
      createTiddlyText(place, columnTemplate.falseText);
  }
};

ListView.columnTypes.TagCheckbox = {
  createHeader: ListView.columnTypes.String.createHeader,
  createItem: function(place, listObject, field, columnTemplate, col, row)
  {
    var e = createTiddlyCheckbox(place, null, listObject[field], this.onChange);
    e.setAttribute("tiddler", listObject.title);
    e.setAttribute("tag", columnTemplate.tag);
  },
  onChange : function(e)
  {
    var tag = this.getAttribute("tag");
    var tiddler = this.getAttribute("tiddler");
    store.setTiddlerTag(tiddler, this.checked, tag);
  }
};

ListView.columnTypes.TiddlerLink = {
  createHeader: ListView.columnTypes.String.createHeader,
  createItem: function(place, listObject, field, columnTemplate, col, row)
  {
    var v = listObject[field];
    if (v != undefined) {
      var link = createTiddlyLink(place, listObject[columnTemplate.tiddlerLink], false, null);
      createTiddlyText(link, listObject[field]);
    }
  }
};

//--
//-- Augmented methods for the JavaScript Number(), Array(), String() and Date() objects
//--

// Clamp a number to a range
Number.prototype.clamp = function(min, max)
{
  var c = this;
  if (c < min)
    c = min;
  if (c > max)
    c = max;
  return c;
};

// Add indexOf function if browser does not support it
if (!Array.indexOf) {
  Array.prototype.indexOf = function(item, from)
  {
    if (!from)
      from = 0;
    for (var i = from; i < this.length; i++) {
      if (this[i] === item)
        return i;
    }
    return -1;
  };
}

// Find an entry in a given field of the members of an array
Array.prototype.findByField = function(field, value)
{
  for (var t = 0; t < this.length; t++) {
    if (this[t][field] == value)
      return t;
  }
  return null;
};

// Return whether an entry exists in an array
Array.prototype.contains = function(item)
{
  return this.indexOf(item) != -1;
};

// Adds, removes or toggles a particular value within an array
//  value - value to add
//  mode - +1 to add value, -1 to remove value, 0 to toggle it
Array.prototype.setItem = function(value, mode)
{
  var p = this.indexOf(value);
  if (mode == 0)
    mode = (p == -1) ? +1 : -1;
  if (mode == +1) {
    if (p == -1)
      this.push(value);
  }
  else if (mode == -1) {
    if (p != -1)
      this.splice(p, 1);
  }
};

// Return whether one of a list of values exists in an array
Array.prototype.containsAny = function(items)
{
  for (var i = 0; i < items.length; i++) {
    if (this.indexOf(items[i]) != -1)
      return true;
  }
  return false;
};

// Return whether all of a list of values exists in an array
Array.prototype.containsAll = function(items)
{
  for (var i = 0; i < items.length; i++) {
    if (this.indexOf(items[i]) == -1)
      return false;
  }
  return true;
};

// Push a new value into an array only if it is not already present in the array. If the optional unique parameter is false, it reverts to a normal push
Array.prototype.pushUnique = function(item, unique)
{
  if (unique === false) {
    this.push(item);
  }
  else {
    if (this.indexOf(item) == -1)
      this.push(item);
  }
};

Array.prototype.remove = function(item)
{
  var p = this.indexOf(item);
  if (p != -1)
    this.splice(p, 1);
};

if (!Array.prototype.map) {
  Array.prototype.map = function(fn, thisObj)
  {
    var scope = thisObj || window;
    var a = [];
    for (var i = 0, j = this.length; i < j; ++i) {
      a.push(fn.call(scope, this[i], i, this));
    }
    return a;
  };
}

// Get characters from the right end of a string
String.prototype.right = function(n)
{
  return n < this.length ? this.slice(this.length - n) : this;
};

// Trim whitespace from both ends of a string
String.prototype.trim = function()
{
  return this.replace(/^\s*|\s*$/g, "");
};

// Convert a string from a CSS style property name to a JavaScript style name ("background-color" -> "backgroundColor")
String.prototype.unDash = function()
{
  var s = this.split("-");
  if (s.length > 1) {
    for (var t = 1; t < s.length; t++)
      s[t] = s[t].substr(0, 1).toUpperCase() + s[t].substr(1);
  }
  return s.join("");
};

// Substitute substrings from an array into a format string that includes '%1'-type specifiers
String.prototype.format = function(substrings)
{
  var subRegExp = /(?:%(\d+))/mg;
  var currPos = 0;
  var r = [];
  do {
    var match = subRegExp.exec(this);
    if (match && match[1]) {
      if (match.index > currPos)
        r.push(this.substring(currPos, match.index));
      r.push(substrings[parseInt(match[1])]);
      currPos = subRegExp.lastIndex;
    }
  }
  while (match);
  if (currPos < this.length)
    r.push(this.substring(currPos, this.length));
  return r.join("");
};

// Escape any special RegExp characters with that character preceded by a backslash
String.prototype.escapeRegExp = function()
{
  var s = "\\^$*+?()=!|,{}[].";
  var c = this;
  for (var t = 0; t < s.length; t++)
    c = c.replace(new RegExp("\\" + s.substr(t, 1), "g"), "\\" + s.substr(t, 1));
  return c;
};

// Convert "\" to "\s", newlines to "\n" (and remove carriage returns)
String.prototype.escapeLineBreaks = function()
{
  return this.replace(/\\/mg, "\\s").replace(/\n/mg, "\\n").replace(/\r/mg, "");
};

// Convert "\n" to newlines, "\b" to " ", "\s" to "\" (and remove carriage returns)
String.prototype.unescapeLineBreaks = function()
{
  return this.replace(/\\n/mg, "\n").replace(/\\b/mg, " ").replace(/\\s/mg, "\\").replace(/\r/mg, "");
};

// Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
String.prototype.htmlEncode = function()
{
  return this.replace(/&/mg, "&amp;").replace(/</mg, "&lt;").replace(/>/mg, "&gt;").replace(/\"/mg, "&quot;");
};

// Convert "&amp;" to &, "&lt;" to <, "&gt;" to > and "&quot;" to "
String.prototype.htmlDecode = function()
{
  return this.replace(/&lt;/mg, "<").replace(/&gt;/mg, ">").replace(/&quot;/mg, "\"").replace(/&amp;/mg, "&");
};

// Convert a string to it's JSON representation by encoding control characters, double quotes and backslash. See json.org
String.prototype.toJSONString = function()
{
  var m = {
    '\b': '\\b',
    '\f': '\\f',
    '\n': '\\n',
    '\r': '\\r',
    '\t': '\\t',
    '"' : '\\"',
    '\\': '\\\\'
  };
  var replaceFn = function(a, b) {
    var c = m[b];
    if (c)
      return c;
    c = b.charCodeAt();
    return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
  };
  if (/["\\\x00-\x1f]/.test(this))
    return '"' + this.replace(/([\x00-\x1f\\"])/g, replaceFn) + '"';
  return '"' + this + '"';
};

// Parse a space-separated string of name:value parameters
// The result is an array of objects:
//   result[0] = object with a member for each parameter name, value of that member being an array of values
//   result[1..n] = one object for each parameter, with 'name' and 'value' members
String.prototype.parseParams = function(defaultName, defaultValue, allowEval, noNames, cascadeDefaults)
{
  var parseToken = function(match, p) {
    var n;
    if (match[p]) // Double quoted
      n = match[p];
    else if (match[p + 1]) // Single quoted
      n = match[p + 1];
    else if (match[p + 2]) // Double-square-bracket quoted
        n = match[p + 2];
      else if (match[p + 3]) // Double-brace quoted
          try {
            n = match[p + 3];
            if (allowEval)
              n = window.eval(n);
          }
          catch(ex) {
            throw "Unable to evaluate {{" + match[p + 3] + "}}: " + exceptionText(ex);
          }
        else if (match[p + 4]) // Unquoted
            n = match[p + 4];
          else if (match[p + 5]) // empty quote
              n = "";
    return n;
  };
  var r = [
    {}
  ];
  var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")";
  var sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')";
  var dblSquare = "(?:\\[\\[((?:\\s|\\S)*?)\\]\\])";
  var dblBrace = "(?:\\{\\{((?:\\s|\\S)*?)\\}\\})";
  var unQuoted = noNames ? "([^\"'\\s]\\S*)" : "([^\"':\\s][^\\s:]*)";
  var emptyQuote = "((?:\"\")|(?:''))";
  var skipSpace = "(?:\\s*)";
  var token = "(?:" + dblQuote + "|" + sngQuote + "|" + dblSquare + "|" + dblBrace + "|" + unQuoted + "|" + emptyQuote + ")";
  var re = noNames ? new RegExp(token, "mg") : new RegExp(skipSpace + token + skipSpace + "(?:(\\:)" + skipSpace + token + ")?", "mg");
  var params = [];
  do {
    var match = re.exec(this);
    if (match) {
      var n = parseToken(match, 1);
      if (noNames) {
        r.push({name:"",value:n});
      }
      else {
        var v = parseToken(match, 8);
        if (v == null && defaultName) {
          v = n;
          n = defaultName;
        }
        else if (v == null && defaultValue) {
          v = defaultValue;
        }
        r.push({name:n,value:v});
        if (cascadeDefaults) {
          defaultName = n;
          defaultValue = v;
        }
      }
    }
  }
  while (match);
  // Summarise parameters into first element
  for (var t = 1; t < r.length; t++) {
    if (r[0][r[t].name])
      r[0][r[t].name].push(r[t].value);
    else
      r[0][r[t].name] = [r[t].value];
  }
  return r;
};

// Process a string list of macro parameters into an array. Parameters can be quoted with "", '',
// [[]], {{ }} or left unquoted (and therefore space-separated). Double-braces {{}} results in
// an *evaluated* parameter: e.g. {{config.options.txtUserName}} results in the current user's name.
String.prototype.readMacroParams = function()
{
  var p = this.parseParams("list", null, true, true);
  var n = [];
  for (var t = 1; t < p.length; t++)
    n.push(p[t].value);
  return n;
};

// Process a string list of unique tiddler names into an array. Tiddler names that have spaces in them must be [[bracketed]]
String.prototype.readBracketedList = function(unique)
{
  var p = this.parseParams("list", null, false, true);
  var n = [];
  for (var t = 1; t < p.length; t++) {
    if (p[t].value)
      n.pushUnique(p[t].value, unique);
  }
  return n;
};

// Returns array with start and end index of chunk between given start and end marker, or undefined.
String.prototype.getChunkRange = function(start, end)
{
  var s = this.indexOf(start);
  if (s != -1) {
    s += start.length;
    var e = this.indexOf(end, s);
    if (e != -1)
      return [s,e];
  }
};

// Replace a chunk of a string given start and end markers
String.prototype.replaceChunk = function(start, end, sub)
{
  var r = this.getChunkRange(start, end);
  return r ? this.substring(0, r[0]) + sub + this.substring(r[1]) : this;
};

// Returns a chunk of a string between start and end markers, or undefined
String.prototype.getChunk = function(start, end)
{
  var r = this.getChunkRange(start, end);
  if (r)
    return this.substring(r[0], r[1]);
};


// Static method to bracket a string with double square brackets if it contains a space
String.encodeTiddlyLink = function(title)
{
  return title.indexOf(" ") == -1 ? title : "[[" + title + "]]";
};

// Static method to encodeTiddlyLink for every item in an array and join them with spaces
String.encodeTiddlyLinkList = function(list)
{
  if (list) {
    var results = [];
    for (var t = 0; t < list.length; t++)
      results.push(String.encodeTiddlyLink(list[t]));
    return results.join(" ");
  }
  else {
    return "";
  }
};

// Convert a string as a sequence of name:"value" pairs into a hashmap
String.prototype.decodeHashMap = function()
{
  var fields = this.parseParams("anon", "", false);
  var r = {};
  for (var t = 1; t < fields.length; t++)
    r[fields[t].name] = fields[t].value;
  return r;
};

// Static method to encode a hashmap into a name:"value"... string
String.encodeHashMap = function(hashmap)
{
  var r = [];
  for (var t in hashmap)
    r.push(t + ':"' + hashmap[t] + '"');
  return r.join(" ");
};

// Static method to left-pad a string with 0s to a certain width
String.zeroPad = function(n, d)
{
  var s = n.toString();
  if (s.length < d)
    s = "000000000000000000000000000".substr(0, d - s.length) + s;
  return s;
};

String.prototype.startsWith = function(prefix)
{
  return !prefix || this.substring(0, prefix.length) == prefix;
};

// Returns the first value of the given named parameter.
function getParam(params, name, defaultValue)
{
  if (!params)
    return defaultValue;
  var p = params[0][name];
  return p ? p[0] : defaultValue;
}

// Returns the first value of the given boolean named parameter.
function getFlag(params, name, defaultValue)
{
  return !!getParam(params, name, defaultValue);
}

// Substitute date components into a string
Date.prototype.formatString = function(template)
{
  var t = template.replace(/0hh12/g, String.zeroPad(this.getHours12(), 2));
  t = t.replace(/hh12/g, this.getHours12());
  t = t.replace(/0hh/g, String.zeroPad(this.getHours(), 2));
  t = t.replace(/hh/g, this.getHours());
  t = t.replace(/mmm/g, config.messages.dates.shortMonths[this.getMonth()]);
  t = t.replace(/0mm/g, String.zeroPad(this.getMinutes(), 2));
  t = t.replace(/mm/g, this.getMinutes());
  t = t.replace(/0ss/g, String.zeroPad(this.getSeconds(), 2));
  t = t.replace(/ss/g, this.getSeconds());
  t = t.replace(/[ap]m/g, this.getAmPm().toLowerCase());
  t = t.replace(/[AP]M/g, this.getAmPm().toUpperCase());
  t = t.replace(/wYYYY/g, this.getYearForWeekNo());
  t = t.replace(/wYY/g, String.zeroPad(this.getYearForWeekNo() - 2000, 2));
  t = t.replace(/YYYY/g, this.getFullYear());
  t = t.replace(/YY/g, String.zeroPad(this.getFullYear() - 2000, 2));
  t = t.replace(/MMM/g, config.messages.dates.months[this.getMonth()]);
  t = t.replace(/0MM/g, String.zeroPad(this.getMonth() + 1, 2));
  t = t.replace(/MM/g, this.getMonth() + 1);
  t = t.replace(/0WW/g, String.zeroPad(this.getWeek(), 2));
  t = t.replace(/WW/g, this.getWeek());
  t = t.replace(/DDD/g, config.messages.dates.days[this.getDay()]);
  t = t.replace(/ddd/g, config.messages.dates.shortDays[this.getDay()]);
  t = t.replace(/0DD/g, String.zeroPad(this.getDate(), 2));
  t = t.replace(/DDth/g, this.getDate() + this.daySuffix());
  t = t.replace(/DD/g, this.getDate());
  var tz = this.getTimezoneOffset();
  var atz = Math.abs(tz);
  t = t.replace(/TZD/g, (tz < 0 ? '+' : '-') + String.zeroPad(Math.floor(atz / 60), 2) + ':' + String.zeroPad(atz % 60, 2));
  t = t.replace(/\\/g, "");
  return t;
};

Date.prototype.getWeek = function()
{
  var dt = new Date(this.getTime());
  var d = dt.getDay();
  if (d == 0) d = 7;// JavaScript Sun=0, ISO Sun=7
  dt.setTime(dt.getTime() + (4 - d) * 86400000);// shift day to Thurs of same week to calculate weekNo
  var n = Math.floor((dt.getTime() - new Date(dt.getFullYear(), 0, 1) + 3600000) / 86400000);
  return Math.floor(n / 7) + 1;
};

Date.prototype.getYearForWeekNo = function()
{
  var dt = new Date(this.getTime());
  var d = dt.getDay();
  if (d == 0) d = 7;// JavaScript Sun=0, ISO Sun=7
  dt.setTime(dt.getTime() + (4 - d) * 86400000);// shift day to Thurs of same week
  return dt.getFullYear();
};

Date.prototype.getHours12 = function()
{
  var h = this.getHours();
  return h > 12 ? h - 12 : ( h > 0 ? h : 12 );
};

Date.prototype.getAmPm = function()
{
  return this.getHours() >= 12 ? config.messages.dates.pm : config.messages.dates.am;
};

Date.prototype.daySuffix = function()
{
  return config.messages.dates.daySuffixes[this.getDate() - 1];
};

// Convert a date to local YYYYMMDDHHMM string format
Date.prototype.convertToLocalYYYYMMDDHHMM = function()
{
  return this.getFullYear() + String.zeroPad(this.getMonth() + 1, 2) + String.zeroPad(this.getDate(), 2) + String.zeroPad(this.getHours(), 2) + String.zeroPad(this.getMinutes(), 2);
};

// Convert a date to UTC YYYYMMDDHHMM string format
Date.prototype.convertToYYYYMMDDHHMM = function()
{
  return this.getUTCFullYear() + String.zeroPad(this.getUTCMonth() + 1, 2) + String.zeroPad(this.getUTCDate(), 2) + String.zeroPad(this.getUTCHours(), 2) + String.zeroPad(this.getUTCMinutes(), 2);
};

// Convert a date to UTC YYYYMMDD.HHMMSSMMM string format
Date.prototype.convertToYYYYMMDDHHMMSSMMM = function()
{
  return this.getUTCFullYear() + String.zeroPad(this.getUTCMonth() + 1, 2) + String.zeroPad(this.getUTCDate(), 2) + "." + String.zeroPad(this.getUTCHours(), 2) + String.zeroPad(this.getUTCMinutes(), 2) + String.zeroPad(this.getUTCSeconds(), 2) + String.zeroPad(this.getUTCMilliseconds(), 4);
};

// Static method to create a date from a UTC YYYYMMDDHHMM format string
Date.convertFromYYYYMMDDHHMM = function(d)
{
  var hh = d.substr(8, 2) || "00";
  var mm = d.substr(10, 2) || "00";
  return new Date(Date.UTC(parseInt(d.substr(0, 4), 10),
          parseInt(d.substr(4, 2), 10) - 1,
          parseInt(d.substr(6, 2), 10),
          parseInt(hh, 10),
          parseInt(mm, 10), 0, 0));
};

//--
//-- Crypto functions and associated conversion routines
//--

// Crypto 'namespace'
function Crypto() {}

// Convert a string to an array of big-endian 32-bit words
Crypto.strToBe32s = function(str)
{
  var be = [];
  var len = Math.floor(str.length / 4);
  var i, j;
  for (i = 0,j = 0; i < len; i++,j += 4) {
    be[i] = ((str.charCodeAt(j) & 0xff) << 24) | ((str.charCodeAt(j + 1) & 0xff) << 16) | ((str.charCodeAt(j + 2) & 0xff) << 8) | (str.charCodeAt(j + 3) & 0xff);
  }
  while (j < str.length) {
    be[j >> 2] |= (str.charCodeAt(j) & 0xff) << (24 - (j * 8) % 32);
    j++;
  }
  return be;
};

// Convert an array of big-endian 32-bit words to a string
Crypto.be32sToStr = function(be)
{
  var str = '';
  for (var i = 0; i < be.length * 32; i += 8) {
    str += String.fromCharCode((be[i >> 5] >>> (24 - i % 32)) & 0xff);
  }
  return str;
};

// Convert an array of big-endian 32-bit words to a hex string
Crypto.be32sToHex = function(be)
{
  var hex = '0123456789ABCDEF';
  var str = '';
  for (var i = 0; i < be.length * 4; i++) {
    str += hex.charAt((be[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) + hex.charAt((be[i >> 2] >> ((3 - i % 4) * 8)) & 0xF);
  }
  return str;
};

// Return, in hex, the SHA-1 hash of a string
Crypto.hexSha1Str = function(str)
{
  return Crypto.be32sToHex(Crypto.sha1Str(str));
};

// Return the SHA-1 hash of a string
Crypto.sha1Str = function(str)
{
  return Crypto.sha1(Crypto.strToBe32s(str), str.length);
};

// Calculate the SHA-1 hash of an array of blen bytes of big-endian 32-bit words
Crypto.sha1 = function(x, blen)
{
  // Add 32-bit integers, wrapping at 32 bits
  function add32(a, b)
  {
    var lsw = (a & 0xFFFF) + (b & 0xFFFF);
    var msw = (a >> 16) + (b >> 16) + (lsw >> 16);
    return (msw << 16) | (lsw & 0xFFFF);
  }

  function AA(a, b, c, d, e)
  {
    b = (b >>> 27) | (b << 5);
    var lsw = (a & 0xFFFF) + (b & 0xFFFF) + (c & 0xFFFF) + (d & 0xFFFF) + (e & 0xFFFF);
    var msw = (a >> 16) + (b >> 16) + (c >> 16) + (d >> 16) + (e >> 16) + (lsw >> 16);
    return (msw << 16) | (lsw & 0xFFFF);
  }

  function RR(w, j)
  {
    var n = w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16];
    return (n >>> 31) | (n << 1);
  }

  var len = blen * 8;
  x[len >> 5] |= 0x80 << (24 - len % 32);
  x[((len + 64 >> 9) << 4) + 15] = len;
  var w = new Array(80);

  var k1 = 0x5A827999;
  var k2 = 0x6ED9EBA1;
  var k3 = 0x8F1BBCDC;
  var k4 = 0xCA62C1D6;

  var h0 = 0x67452301;
  var h1 = 0xEFCDAB89;
  var h2 = 0x98BADCFE;
  var h3 = 0x10325476;
  var h4 = 0xC3D2E1F0;

  for (var i = 0; i < x.length; i += 16) {
    var j = 0;
    var t;
    var a = h0;
    var b = h1;
    var c = h2;
    var d = h3;
    var e = h4;
    while (j < 16) {
      w[j] = x[i + j];
      t = AA(e, a, d ^ (b & (c ^ d)), w[j], k1);
      e = d;
      d = c;
      c = (b >>> 2) | (b << 30);
      b = a;
      a = t;
      j++;
    }
    while (j < 20) {
      w[j] = RR(w, j);
      t = AA(e, a, d ^ (b & (c ^ d)), w[j], k1);
      e = d;
      d = c;
      c = (b >>> 2) | (b << 30);
      b = a;
      a = t;
      j++;
    }
    while (j < 40) {
      w[j] = RR(w, j);
      t = AA(e, a, b ^ c ^ d, w[j], k2);
      e = d;
      d = c;
      c = (b >>> 2) | (b << 30);
      b = a;
      a = t;
      j++;
    }
    while (j < 60) {
      w[j] = RR(w, j);
      t = AA(e, a, (b & c) | (d & (b | c)), w[j], k3);
      e = d;
      d = c;
      c = (b >>> 2) | (b << 30);
      b = a;
      a = t;
      j++;
    }
    while (j < 80) {
      w[j] = RR(w, j);
      t = AA(e, a, b ^ c ^ d, w[j], k4);
      e = d;
      d = c;
      c = (b >>> 2) | (b << 30);
      b = a;
      a = t;
      j++;
    }
    h0 = add32(h0, a);
    h1 = add32(h1, b);
    h2 = add32(h2, c);
    h3 = add32(h3, d);
    h4 = add32(h4, e);
  }
  return [h0,h1,h2,h3,h4];
};

//--
//-- RGB colour object
//--

// Construct an RGB colour object from a '#rrggbb', '#rgb' or 'rgb(n,n,n)' string or from separate r,g,b values
function RGB(r, g, b)
{
  this.r = 0;
  this.g = 0;
  this.b = 0;
  if (typeof r == "string") {
    if (r.substr(0, 1) == "#") {
      if (r.length == 7) {
        this.r = parseInt(r.substr(1, 2), 16) / 255;
        this.g = parseInt(r.substr(3, 2), 16) / 255;
        this.b = parseInt(r.substr(5, 2), 16) / 255;
      }
      else {
        this.r = parseInt(r.substr(1, 1), 16) / 15;
        this.g = parseInt(r.substr(2, 1), 16) / 15;
        this.b = parseInt(r.substr(3, 1), 16) / 15;
      }
    }
    else {
      var rgbPattern = /rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/;
      var c = r.match(rgbPattern);
      if (c) {
        this.r = parseInt(c[1], 10) / 255;
        this.g = parseInt(c[2], 10) / 255;
        this.b = parseInt(c[3], 10) / 255;
      }
    }
  }
  else {
    this.r = r;
    this.g = g;
    this.b = b;
  }
  return this;
}

// Mixes this colour with another in a specified proportion
// c = other colour to mix
// f = 0..1 where 0 is this colour and 1 is the new colour
// Returns an RGB object
RGB.prototype.mix = function(c, f)
{
  return new RGB(this.r + (c.r - this.r) * f, this.g + (c.g - this.g) * f, this.b + (c.b - this.b) * f);
};

// Return an rgb colour as a #rrggbb format hex string
RGB.prototype.toString = function()
{
  return "#" + ("0" + Math.floor(this.r.clamp(0, 1) * 255).toString(16)).right(2) +
         ("0" + Math.floor(this.g.clamp(0, 1) * 255).toString(16)).right(2) +
         ("0" + Math.floor(this.b.clamp(0, 1) * 255).toString(16)).right(2);
};

//--
//-- DOM utilities - many derived from www.quirksmode.org
//--

function drawGradient(place, horiz, locolors, hicolors)
{
  if (!hicolors)
    hicolors = locolors;
  for (var t = 0; t <= 100; t += 2) {
    var bar = document.createElement("div");
    place.appendChild(bar);
    bar.style.position = "absolute";
    bar.style.left = horiz ? t + "%" : 0;
    bar.style.top = horiz ? 0 : t + "%";
    bar.style.width = horiz ? (101 - t) + "%" : "100%";
    bar.style.height = horiz ? "100%" : (101 - t) + "%";
    bar.style.zIndex = -1;
    var p = t / 100 * (locolors.length - 1);
    bar.style.backgroundColor = hicolors[Math.floor(p)].mix(locolors[Math.ceil(p)], p - Math.floor(p)).toString();
  }
}

function createTiddlyText(parent, text)
{
  return parent.appendChild(document.createTextNode(text));
}

function createTiddlyCheckbox(parent, caption, checked, onChange)
{
  var cb = document.createElement("input");
  cb.setAttribute("type", "checkbox");
  cb.onclick = onChange;
  parent.appendChild(cb);
  cb.checked = checked;
  cb.className = "chkOptionInput";
  if (caption)
    wikify(caption, parent);
  return cb;
}

function createTiddlyElement(parent, element, id, className, text, attribs)
{
  var e = document.createElement(element);
  if (className != null)
    e.className = className;
  if (id != null)
    e.setAttribute("id", id);
  if (text != null)
    e.appendChild(document.createTextNode(text));
  if (attribs) {
    for (var n in attribs) {
      e.setAttribute(n, attribs[n]);
    }
  }
  if (parent != null)
    parent.appendChild(e);
  return e;
}

function addEvent(obj, type, fn)
{
  if (obj.attachEvent) {
    obj['e' + type + fn] = fn;
    obj[type + fn] = function() {obj['e' + type + fn](window.event);};
    obj.attachEvent('on' + type, obj[type + fn]);
  }
  else {
    obj.addEventListener(type, fn, false);
  }
}

function removeEvent(obj, type, fn)
{
  if (obj.detachEvent) {
    obj.detachEvent('on' + type, obj[type + fn]);
    obj[type + fn] = null;
  }
  else {
    obj.removeEventListener(type, fn, false);
  }
}

function addClass(e, className)
{
  var currClass = e.className.split(" ");
  if (currClass.indexOf(className) == -1)
    e.className += " " + className;
}

function removeClass(e, className)
{
  var currClass = e.className.split(" ");
  var i = currClass.indexOf(className);
  while (i != -1) {
    currClass.splice(i, 1);
    i = currClass.indexOf(className);
  }
  e.className = currClass.join(" ");
}

function hasClass(e, className)
{
  if (e.className && e.className.split(" ").indexOf(className) != -1) {
    return true;
  }
  return false;
}

// Find the closest relative with a given property value (property defaults to tagName, relative defaults to parentNode)
function findRelated(e, value, name, relative)
{
  name = name || "tagName";
  relative = relative || "parentNode";
  if (name == "className") {
    while (e && !hasClass(e, value)) {
      e = e[relative];
    }
  }
  else {
    while (e && e[name] != value) {
      e = e[relative];
    }
  }
  return e;
}

// Resolve the target object of an event
function resolveTarget(e)
{
  var obj;
  if (e.target)
    obj = e.target;
  else if (e.srcElement)
    obj = e.srcElement;
  if (obj.nodeType == 3) // defeat Safari bug
    obj = obj.parentNode;
  return obj;
}

// Prevent an event from bubbling
function stopEvent(e)
{
  var ev = e || window.event;
  ev.cancelBubble = true;
  if (ev.stopPropagation) ev.stopPropagation();
  return false;
}

// Return the content of an element as plain text with no formatting
function getPlainText(e)
{
  var text = "";
  if (e.innerText)
    text = e.innerText;
  else if (e.textContent)
    text = e.textContent;
  return text;
}

// Get the scroll position for window.scrollTo necessary to scroll a given element into view
function ensureVisible(e)
{
  var posTop = findPosY(e);
  var posBot = posTop + e.offsetHeight;
  var winTop = findScrollY();
  var winHeight = findWindowHeight();
  var winBot = winTop + winHeight;
  if (posTop < winTop) {
    return posTop;
  }
  else if (posBot > winBot) {
    if (e.offsetHeight < winHeight)
      return posTop - (winHeight - e.offsetHeight);
    else
      return posTop;
  }
  else {
    return winTop;
  }
}

// Get the current width of the display window
function findWindowWidth()
{
  return window.innerWidth || document.documentElement.clientWidth;
}

// Get the current height of the display window
function findWindowHeight()
{
  return window.innerHeight || document.documentElement.clientHeight;
}

// Get the current horizontal page scroll position
function findScrollX()
{
  return window.scrollX || document.documentElement.scrollLeft;
}

// Get the current vertical page scroll position
function findScrollY()
{
  return window.scrollY || document.documentElement.scrollTop;
}

function findPosX(obj)
{
  var curleft = 0;
  while (obj.offsetParent) {
    curleft += obj.offsetLeft;
    obj = obj.offsetParent;
  }
  return curleft;
}

function findPosY(obj)
{
  var curtop = 0;
  while (obj.offsetParent) {
    curtop += obj.offsetTop;
    obj = obj.offsetParent;
  }
  return curtop;
}

// Blur a particular element
function blurElement(e)
{
  if (e && e.focus && e.blur) {
    e.focus();
    e.blur();
  }
}

// Create a non-breaking space
function insertSpacer(place)
{
  var e = document.createTextNode(String.fromCharCode(160));
  if (place)
    place.appendChild(e);
  return e;
}

// Remove all children of a node
function removeChildren(e)
{
  while (e && e.hasChildNodes())
    removeNode(e.firstChild);
}

// Remove a node and all it's children
function removeNode(e)
{
  scrubNode(e);
  e.parentNode.removeChild(e);
}

// Remove any event handlers or non-primitve custom attributes
function scrubNode(e)
{
  if (!config.browser.isIE)
    return;
  var att = e.attributes;
  if (att) {
    for (var t = 0; t < att.length; t++) {
      var n = att[t].name;
      if (n !== 'style' && (typeof e[n] === 'function' || (typeof e[n] === 'object' && e[n] != null))) {
        try {
          e[n] = null;
        }
        catch(ex) {
        }
      }
    }
  }
  var c = e.firstChild;
  while (c) {
    scrubNode(c);
    c = c.nextSibling;
  }
}

// Add a stylesheet, replacing any previous custom stylesheet
function setStylesheet(s, id, doc)
{
  if (!id)
    id = "customStyleSheet";
  if (!doc)
    doc = document;
  var n = doc.getElementById(id);
  if (doc.createStyleSheet) {
    // Test for IE's non-standard createStyleSheet method
    if (n)
      n.parentNode.removeChild(n);
    // This failed without the &nbsp;
    doc.getElementsByTagName("head")[0].insertAdjacentHTML("beforeEnd", "&nbsp;<style id='" + id + "'>" + s + "</style>");
  }
  else {
    if (n) {
      n.replaceChild(doc.createTextNode(s), n.firstChild);
    }
    else {
      n = doc.createElement("style");
      n.type = "text/css";
      n.id = id;
      n.appendChild(doc.createTextNode(s));
      doc.getElementsByTagName("head")[0].appendChild(n);
    }
  }
}

function removeStyleSheet(id)
{
  var e = document.getElementById(id);
  if (e)
    e.parentNode.removeChild(e);
}

// Force the browser to do a document reflow when needed to workaround browser bugs
function forceReflow()
{
  if (config.browser.isGecko) {
    setStylesheet("body {top:0px;margin-top:0px;}", "forceReflow");
    setTimeout(function() {setStylesheet("", "forceReflow");}, 1);
  }
}

// Replace the current selection of a textarea or text input and scroll it into view
function replaceSelection(e, text)
{
  if (e.setSelectionRange) {
    var oldpos = e.selectionStart;
    var isRange = e.selectionEnd > e.selectionStart;
    e.value = e.value.substr(0, e.selectionStart) + text + e.value.substr(e.selectionEnd);
    e.setSelectionRange(isRange ? oldpos : oldpos + text.length, oldpos + text.length);
    var linecount = e.value.split('\n').length;
    var thisline = e.value.substr(0, e.selectionStart).split('\n').length - 1;
    e.scrollTop = Math.floor((thisline - e.rows / 2) * e.scrollHeight / linecount);
  }
  else if (document.selection) {
    var range = document.selection.createRange();
    if (range.parentElement() == e) {
      var isCollapsed = range.text == "";
      range.text = text;
      if (!isCollapsed) {
        range.moveStart('character', -text.length);
        range.select();
      }
    }
  }
}

// Returns the text of the given (text) node, possibly merging subsequent text nodes
function getNodeText(e)
{
  var t = "";
  while (e && e.nodeName == "#text") {
    t += e.nodeValue;
    e = e.nextSibling;
  }
  return t;
}

// Returns true if the element e has a given ancestor element
function isDescendant(e, ancestor)
{
  while (e) {
    if (e === ancestor)
      return true;
    e = e.parentNode;
  }
  return false;
}

//--
//-- LoaderBase and SaverBase
//--

function LoaderBase() {}

LoaderBase.prototype.loadTiddler = function(store, node, tiddlers)
{
  var title = this.getTitle(store, node);
  if (safeMode && store.isShadowTiddler(title))
    return;
  if (title) {
    var tiddler = store.createTiddler(title);
    this.internalizeTiddler(store, tiddler, title, node);
    tiddlers.push(tiddler);
  }
};

LoaderBase.prototype.loadTiddlers = function(store, nodes)
{
  var tiddlers = [];
  for (var t = 0; t < nodes.length; t++) {
    try {
      this.loadTiddler(store, nodes[t], tiddlers);
    }
    catch(ex) {
      showException(ex, config.messages.tiddlerLoadError.format([this.getTitle(store, nodes[t])]));
    }
  }
  return tiddlers;
};

function SaverBase() {}

SaverBase.prototype.externalize = function(store)
{
  var results = [];
  var tiddlers = store.getTiddlers("title");
  for (var t = 0; t < tiddlers.length; t++) {
    if (!tiddlers[t].doNotSave())
      results.push(this.externalizeTiddler(store, tiddlers[t]));
  }
  return results.join("\n");
};

//--
//-- TW21Loader (inherits from LoaderBase)
//--

function TW21Loader() {}

TW21Loader.prototype = new LoaderBase();

TW21Loader.prototype.getTitle = function(store, node)
{
  var title = null;
  if (node.getAttribute) {
    title = node.getAttribute("title");
    if (!title)
      title = node.getAttribute("tiddler");
  }
  if (!title && node.id) {
    var lenPrefix = store.idPrefix.length;
    if (node.id.substr(0, lenPrefix) == store.idPrefix)
      title = node.id.substr(lenPrefix);
  }
  return title;
};

TW21Loader.prototype.internalizeTiddler = function(store, tiddler, title, node)
{
  var e = node.firstChild;
  var text = null;
  if (node.getAttribute("tiddler")) {
    text = getNodeText(e).unescapeLineBreaks();
  }
  else {
    while (e.nodeName != "PRE" && e.nodeName != "pre") {
      e = e.nextSibling;
    }
    text = e.innerHTML.replace(/\r/mg, "").htmlDecode();
  }
  var modifier = node.getAttribute("modifier");
  var c = node.getAttribute("created");
  var m = node.getAttribute("modified");
  var created = c ? Date.convertFromYYYYMMDDHHMM(c) : version.date;
  var modified = m ? Date.convertFromYYYYMMDDHHMM(m) : created;
  var tags = node.getAttribute("tags");
  var fields = {};
  var attrs = node.attributes;
  for (var i = attrs.length - 1; i >= 0; i--) {
    var name = attrs[i].name;
    if (attrs[i].specified && !TiddlyWiki.isStandardField(name)) {
      fields[name] = attrs[i].value.unescapeLineBreaks();
    }
  }
  tiddler.assign(title, text, modifier, modified, tags, created, fields);
  return tiddler;
};

//--
//-- TW21Saver (inherits from SaverBase)
//--

function TW21Saver() {}

TW21Saver.prototype = new SaverBase();

TW21Saver.prototype.externalizeTiddler = function(store, tiddler)
{
  try {
    var extendedAttributes = "";
    var usePre = config.options.chkUsePreForStorage;
    store.forEachField(tiddler,
            function(tiddler, fieldName, value) {
              // don't store stuff from the temp namespace
              if (typeof value != "string")
                value = "";
              if (!fieldName.match(/^temp\./))
                extendedAttributes += ' %0="%1"'.format([fieldName,value.escapeLineBreaks().htmlEncode()]);
            }, true);
    var created = tiddler.created;
    var modified = tiddler.modified;
    var attributes = tiddler.modifier ? ' modifier="' + tiddler.modifier.htmlEncode() + '"' : "";
    attributes += (usePre && created == version.date) ? "" : ' created="' + created.convertToYYYYMMDDHHMM() + '"';
    attributes += (usePre && modified == created) ? "" : ' modified="' + modified.convertToYYYYMMDDHHMM() + '"';
    var tags = tiddler.getTags();
    if (!usePre || tags)
      attributes += ' tags="' + tags.htmlEncode() + '"';
    return ('<div %0="%1"%2%3>%4</' + 'div>').format([
      usePre ? "title" : "tiddler",
      tiddler.title.htmlEncode(),
      attributes,
      extendedAttributes,
      usePre ? "\n<pre>" + tiddler.text.htmlEncode() + "</pre>\n" : tiddler.text.escapeLineBreaks().htmlEncode()
    ]);
  }
  catch (ex) {
    throw exceptionText(ex, config.messages.tiddlerSaveError.format([tiddler.title]));
  }
};

//]]>
</script>
<script type="text/javascript">
  //<![CDATA[
  if (useJavaSaver)
    document.write("<applet style='position:absolute;left:-1px' name='TiddlySaver' code='TiddlySaver.class' archive='TiddlySaver.jar' width='1' height='1'></applet>");
  //]]>
</script>
<!--POST-SCRIPT-START-->

<!--POST-SCRIPT-END-->
</body>
</html>
